// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= PoseAssetFactory.cpp: Factory for PoseAsset =============================================================================*/ #include "Factories/PoseAssetFactory.h" #include "Modules/ModuleManager.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SWindow.h" #include "Widgets/Layout/SBorder.h" #include "EditorStyleSet.h" #include "Animation/Skeleton.h" #include "Animation/AnimSequence.h" #include "Editor.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" #include "Animation/PoseAsset.h" #include "Widgets/Text/STextBlock.h" #include "Widgets/Input/SMultiLineEditableTextBox.h" #include "Widgets/Layout/SUniformGridPanel.h" #include "Widgets/Input/SButton.h" #include "Widgets/Layout/SSeparator.h" #define LOCTEXT_NAMESPACE "PoseAssetFactory" DECLARE_DELEGATE_ThreeParams(FOnPoseConfigureUserAction, bool /*bCreate*/, UAnimSequence* /*InSequence*/, const TArray& /*InPoseNames*/); class SPoseConfigureWindow: public SWindow { public: SLATE_BEGIN_ARGS(SPoseConfigureWindow) {} SLATE_ARGUMENT(UAnimSequence*, SourceSequence) SLATE_ARGUMENT(FOnPoseConfigureUserAction, UserActionHandler) SLATE_ARGUMENT(FSimpleDelegate, OnCreateCanceled) SLATE_END_ARGS() public: void Construct(const FArguments& InArgs) { UserActionHandler = InArgs._UserActionHandler; SourceSequence = InArgs._SourceSequence; // Load the content browser module to display an asset picker FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); FAssetPickerConfig AssetPickerConfig; /** The asset picker will only show sequences */ AssetPickerConfig.Filter.ClassNames.Add(UAnimSequence::StaticClass()->GetFName()); /** The delegate that fires when an asset was selected */ AssetPickerConfig.OnAssetSelected = FOnAssetSelected::CreateRaw(this, &SPoseConfigureWindow::OnSourceAnimationSelected); if (SourceSequence != nullptr) { AssetPickerConfig.InitialAssetSelection = FAssetData(SourceSequence); } /** The default view mode should be a list view */ AssetPickerConfig.InitialAssetViewType = EAssetViewType::List; SWindow::Construct(SWindow::FArguments() .Title(LOCTEXT("CreatePoseAssetOptions", "Create Pose Asset")) .SizingRule(ESizingRule::UserSized) .ClientSize(FVector2D(500, 600)) .SupportsMinimize(false) .SupportsMaximize(false) [SNew(SBorder) .BorderImage(FEditorStyle::GetBrush("Menu.Background")) [SNew(SVerticalBox) + SVerticalBox::Slot().FillHeight(0.4).Padding(3, 3)[SNew(SVerticalBox) + SVerticalBox::Slot().AutoHeight().Padding(2, 2)[SNew(STextBlock).Text(FText::FromString(TEXT("Select Source Animation")))] + SVerticalBox::Slot().Padding(2, 2)[ContentBrowserModule.Get().CreateAssetPicker(AssetPickerConfig)]] + SVerticalBox::Slot() .AutoHeight() [SNew(SSeparator) .Orientation(Orient_Horizontal)] + SVerticalBox::Slot() .FillHeight(0.4) .Padding(3, 3) [SNew(SVerticalBox) + SVerticalBox::Slot() .AutoHeight() .Padding(2, 2) [SNew(STextBlock) .Text(FText::FromString(TEXT("[OPTIONAL] Pose Names (one name for each line)")))] + SVerticalBox::Slot() .Padding(2, 2) [SAssignNew(TextBlock, SMultiLineEditableTextBox) .HintText(FText::FromString(TEXT("Type one pose name for each line...")))]] + SVerticalBox::Slot() .AutoHeight() [SNew(SUniformGridPanel) .SlotPadding(FEditorStyle::GetMargin("StandardDialog.SlotPadding")) .MinDesiredSlotWidth(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotWidth")) .MinDesiredSlotHeight(FEditorStyle::GetFloat("StandardDialog.MinDesiredSlotHeight")) + SUniformGridPanel::Slot(0, 0) [SNew(SButton) .Text(LOCTEXT("Accept", "Accept")) .HAlign(HAlign_Center) .ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) .IsEnabled(this, &SPoseConfigureWindow::CanAccept) .OnClicked_Raw(this, &SPoseConfigureWindow::OnAccept)] + SUniformGridPanel::Slot(1, 0) [SNew(SButton) .Text(LOCTEXT("Cancel", "Cancel")) .HAlign(HAlign_Center) .ContentPadding(FEditorStyle::GetMargin("StandardDialog.ContentPadding")) .OnClicked_Raw(this, &SPoseConfigureWindow::OnCancel)] ]]]); } bool CanAccept() const { return SourceSequence != nullptr && SourceSequence->GetSkeleton() && UserActionHandler.IsBound(); } FReply OnAccept() { if (CanAccept()) { // parse posenames FText InputText = TextBlock->GetPlainText(); FString InputString = InputText.ToString(); PoseNames.Reset(); InputString.ParseIntoArrayLines(PoseNames); UserActionHandler.Execute(true, SourceSequence, PoseNames); } RequestDestroyWindow(); return FReply::Handled(); } FReply OnCancel() { if (UserActionHandler.IsBound()) { UserActionHandler.Execute(false, nullptr, PoseNames); } RequestDestroyWindow(); return FReply::Handled(); } void OnSourceAnimationSelected(const FAssetData& SelectedAsset) { SourceSequence = Cast(SelectedAsset.GetAsset()); } private: UAnimSequence* SourceSequence; TArray PoseNames; FOnPoseConfigureUserAction UserActionHandler; TSharedPtr TextBlock; }; UPoseAssetFactory::UPoseAssetFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bCreateNew = true; SupportedClass = UPoseAsset::StaticClass(); } bool UPoseAssetFactory::ConfigureProperties() { TSharedPtr PickerWindow = SNew(SPoseConfigureWindow) .SourceSequence(SourceAnimation) .UserActionHandler(FOnPoseConfigureUserAction::CreateUObject(this, &UPoseAssetFactory::OnWindowUserActionDelegate)); // have to clear after setting it because // you could use close button without clicking cancel SourceAnimation = nullptr; GEditor->EditorAddModalWindow(PickerWindow.ToSharedRef()); PickerWindow.Reset(); return SourceAnimation != nullptr; } UObject* UPoseAssetFactory::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) { if (SourceAnimation) { // Use the skeleton from the source animation TargetSkeleton = SourceAnimation->GetSkeleton(); UPoseAsset* PoseAsset = NewObject(InParent, Class, Name, Flags); TArray InputPoseNames; if (PoseNames.Num() > 0) { for (int32 Index = 0; Index < PoseNames.Num(); ++Index) { FName PoseName = FName(*PoseNames[Index]); FSmartName NewName; if (TargetSkeleton->GetSmartNameByName(USkeleton::AnimCurveMappingName, PoseName, NewName) == false) { // if failed, add it TargetSkeleton->AddSmartNameAndModify(USkeleton::AnimCurveMappingName, PoseName, NewName); } // we want same names in multiple places InputPoseNames.AddUnique(NewName); } } PoseAsset->CreatePoseFromAnimation(SourceAnimation, &InputPoseNames); PoseAsset->SetSkeleton(TargetSkeleton); return PoseAsset; } return NULL; } void UPoseAssetFactory::OnWindowUserActionDelegate(bool bCreate, UAnimSequence* InSequence, const TArray& InPoseNames) { if (bCreate && InSequence) { SourceAnimation = InSequence; PoseNames = InPoseNames; } else { SourceAnimation = nullptr; PoseNames.Reset(); } } #undef LOCTEXT_NAMESPACE