├── Resources ├── Icon128.png └── importsettings.png ├── README.md ├── Source └── a2fEditor │ ├── Public │ ├── a2fEditor.h │ ├── a2fFactory.h │ ├── a2fImportUI.h │ ├── a2fAssetImportData.h │ └── a2fOptionWindow.h │ ├── Private │ ├── a2fAssetImportData.cpp │ ├── a2fImportUI.cpp │ ├── a2fEditor.cpp │ ├── a2fOptionWindow.cpp │ └── a2fFactory.cpp │ └── a2fEditor.Build.cs ├── a2f.uplugin ├── LICENSE └── .gitignore /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantreble/a2f/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /Resources/importsettings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dantreble/a2f/HEAD/Resources/importsettings.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # a2f 2 | Unreal plugin to import json blendshape animations generated by audio2face in Nvidia Omniverse 3 | 4 | It can convert a blend shape channel to a bone transform: 5 | 6 | ![Import settings example](/Resources/importsettings.png) 7 | 8 | It supports reimport and batch import. 9 | 10 | -------------------------------------------------------------------------------- /Source/a2fEditor/Public/a2fEditor.h: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class Fa2fEditorModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | -------------------------------------------------------------------------------- /a2f.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "0.1", 5 | "FriendlyName": "a2f", 6 | "Description": "Import JSON blendshape a2f animations.", 7 | "Category": "Other", 8 | "CreatedBy": "Dan Treble", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": true, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "a2fEditor", 20 | "Type": "Editor", 21 | "LoadingPhase": "PreDefault" 22 | } 23 | ] 24 | } -------------------------------------------------------------------------------- /Source/a2fEditor/Private/a2fAssetImportData.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | 4 | #include "a2fAssetImportData.h" 5 | 6 | 7 | void Ua2fAssetImportData::GetCurvesToStrip(TSet &CurvesToStrip) const 8 | { 9 | for (const FCurveDrivenBoneTransform &CurveDrivenBoneTransform : CurveDrivenBoneTransforms) 10 | { 11 | for (const FCurveDrivenTransform &CurveDrivenTransform : CurveDrivenBoneTransform.CurveDrivenTransforms) 12 | { 13 | if(CurveDrivenTransform.StripCurveTrack) 14 | { 15 | CurvesToStrip.Add(CurveDrivenTransform.Curve); 16 | } 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Source/a2fEditor/Private/a2fImportUI.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | 4 | #include "a2fImportUI.h" 5 | 6 | #include "a2fAssetImportData.h" 7 | #include "AnimationUtils.h" 8 | 9 | 10 | Ua2fImportUI::Ua2fImportUI() 11 | { 12 | ResetToDefault(); 13 | } 14 | 15 | void Ua2fImportUI::ResetToDefault() 16 | { 17 | AnimSequenceImportData = CreateDefaultSubobject(TEXT("AnimSequenceImportData"), true); 18 | 19 | BoneCompressionSettings = FAnimationUtils::GetDefaultAnimationBoneCompressionSettings(); 20 | 21 | CurveCompressionSettings = FAnimationUtils::GetDefaultAnimationCurveCompressionSettings(); 22 | } 23 | 24 | -------------------------------------------------------------------------------- /Source/a2fEditor/Private/a2fEditor.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | #include "a2fEditor.h" 4 | 5 | #define LOCTEXT_NAMESPACE "Fa2fEditorModule" 6 | 7 | void Fa2fEditorModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void Fa2fEditorModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(Fa2fEditorModule, a2f) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Daniel Treble 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Visual Studio 2015 user specific files 2 | .vs/ 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | 22 | # Compiled Static libraries 23 | *.lai 24 | *.la 25 | *.a 26 | *.lib 27 | 28 | # Executables 29 | *.exe 30 | *.out 31 | *.app 32 | *.ipa 33 | 34 | # These project files can be generated by the engine 35 | *.xcodeproj 36 | *.xcworkspace 37 | *.sln 38 | *.suo 39 | *.opensdf 40 | *.sdf 41 | *.VC.db 42 | *.VC.opendb 43 | 44 | # Precompiled Assets 45 | SourceArt/**/*.png 46 | SourceArt/**/*.tga 47 | 48 | # Binary Files 49 | Binaries/* 50 | Plugins/*/Binaries/* 51 | 52 | # Builds 53 | Build/* 54 | 55 | # Whitelist PakBlacklist-.txt files 56 | !Build/*/ 57 | Build/*/** 58 | !Build/*/PakBlacklist*.txt 59 | 60 | # Don't ignore icon files in Build 61 | !Build/**/*.ico 62 | 63 | # Built data for maps 64 | *_BuiltData.uasset 65 | 66 | # Configuration files generated by the Editor 67 | Saved/* 68 | 69 | # Compiled source files for the engine to use 70 | Intermediate/* 71 | Plugins/*/Intermediate/* 72 | 73 | # Cache files for the editor to use 74 | DerivedDataCache/* 75 | -------------------------------------------------------------------------------- /Source/a2fEditor/a2fEditor.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class a2fEditor : ModuleRules 6 | { 7 | public a2fEditor(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | // ... add other public dependencies that you statically link with here ... 30 | } 31 | ); 32 | 33 | 34 | PrivateDependencyModuleNames.AddRange( 35 | new string[] 36 | { 37 | "UnrealEd", 38 | "CoreUObject", 39 | "Engine", 40 | "Slate", 41 | "SlateCore", 42 | "Json", 43 | "JsonUtilities", 44 | "MainFrame", 45 | "ApplicationCore", 46 | "InputCore", 47 | "EditorStyle" 48 | // ... add private dependencies that you statically link with here ... 49 | } 50 | ); 51 | 52 | 53 | DynamicallyLoadedModuleNames.AddRange( 54 | new string[] 55 | { 56 | // ... add any modules that your module loads dynamically here ... 57 | } 58 | ); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Source/a2fEditor/Public/a2fFactory.h: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Factories/Factory.h" 7 | #include "EditorReimportHandler.h" 8 | #include "a2fFactory.generated.h" 9 | 10 | /** 11 | * 12 | */ 13 | UCLASS() 14 | class A2FEDITOR_API Ua2fFactory : public UFactory, public FReimportHandler 15 | { 16 | GENERATED_UCLASS_BODY() 17 | 18 | public: 19 | 20 | Ua2fFactory(); 21 | 22 | virtual void PostInitProperties() override; 23 | 24 | virtual bool FactoryCanImport(const FString& Filename) override; 25 | 26 | virtual UObject* FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, 27 | UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn, 28 | bool& bOutOperationCanceled) override; 29 | virtual void CleanUp() override; 30 | 31 | 32 | // FReimportHandler 33 | virtual int32 GetPriority() const override; 34 | virtual bool CanReimport(UObject* Obj, TArray& OutFilenames) override; 35 | virtual void SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths) override; 36 | virtual EReimportResult::Type Reimport(UObject* Obj) override; 37 | 38 | 39 | private: 40 | 41 | UPROPERTY() 42 | class Ua2fImportUI* ImportUI; 43 | 44 | 45 | bool bShowOption; 46 | 47 | /** true if the import operation was canceled. */ 48 | bool bOperationCanceled; 49 | }; 50 | 51 | 52 | -------------------------------------------------------------------------------- /Source/a2fEditor/Public/a2fImportUI.h: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "a2fImportUI.generated.h" 7 | 8 | /** 9 | * 10 | */ 11 | 12 | UCLASS(BlueprintType, HideCategories = Object, MinimalAPI) 13 | class Ua2fImportUI : public UObject 14 | { 15 | public: 16 | Ua2fImportUI(); 17 | 18 | private: 19 | GENERATED_BODY() 20 | 21 | public: 22 | void ResetToDefault(); 23 | 24 | /** Skeleton to use for imported asset. When importing a mesh, leaving this as "None" will create a new skeleton. When importing an animation this MUST be specified to import the asset. */ 25 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ImportSettings, meta = (ImportType = "SkeletalMesh|Animation")) 26 | class USkeleton* Skeleton; 27 | 28 | /** The bone compression settings used to compress bones in this sequence. */ 29 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ImportSettings) 30 | class UAnimBoneCompressionSettings* BoneCompressionSettings; 31 | 32 | /** The curve compression settings used to compress curves in this sequence. */ 33 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = ImportSettings) 34 | class UAnimCurveCompressionSettings* CurveCompressionSettings; 35 | 36 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Transient, Instanced, Category = ImportSettings) 37 | class Ua2fAssetImportData* AnimSequenceImportData; 38 | 39 | 40 | bool bIsReimport; 41 | }; 42 | -------------------------------------------------------------------------------- /Source/a2fEditor/Public/a2fAssetImportData.h: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "EditorFramework/AssetImportData.h" 7 | #include "a2fAssetImportData.generated.h" 8 | 9 | /** 10 | * 11 | */ 12 | 13 | USTRUCT(BlueprintType) 14 | struct FCurveDrivenTransform 15 | { 16 | GENERATED_BODY() 17 | 18 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 19 | FString Curve; 20 | 21 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 22 | bool StripCurveTrack = true; 23 | 24 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 25 | FTransform Transform; 26 | }; 27 | 28 | USTRUCT(BlueprintType) 29 | struct FCurveDrivenBoneTransform 30 | { 31 | GENERATED_BODY() 32 | 33 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 34 | FName Bone; 35 | 36 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 37 | TArray CurveDrivenTransforms; 38 | }; 39 | 40 | UCLASS(BlueprintType) 41 | class A2FEDITOR_API Ua2fAssetImportData : public UAssetImportData 42 | { 43 | GENERATED_BODY() 44 | 45 | public: 46 | 47 | UFUNCTION() 48 | void GetCurvesToStrip(TSet& CurvesToStrip) const; 49 | 50 | /** Use this option to specify a sample rate for the imported animation*/ 51 | UPROPERTY(EditAnywhere, BlueprintReadWrite, meta = (ToolTip = "Animation frames per second", ClampMin = 0, UIMin = 0, ClampMax = 48000, UIMax = 60)) 52 | int32 FrameRate = 30; 53 | 54 | UPROPERTY(EditAnywhere, BlueprintReadWrite) 55 | TEnumAsByte AdditiveAnimType = AAT_LocalSpaceBase; 56 | 57 | UPROPERTY(EditAnywhere,BlueprintReadWrite) 58 | TArray CurveDrivenBoneTransforms; 59 | 60 | }; 61 | -------------------------------------------------------------------------------- /Source/a2fEditor/Public/a2fOptionWindow.h: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "InputCoreTypes.h" 7 | #include "Widgets/DeclarativeSyntaxSupport.h" 8 | #include "Input/Reply.h" 9 | #include "Widgets/SCompoundWidget.h" 10 | #include "Widgets/SWindow.h" 11 | #include "a2fImportUI.h" 12 | 13 | class SButton; 14 | 15 | class A2FEDITOR_API Sa2fOptionWindow : public SCompoundWidget 16 | { 17 | public: 18 | SLATE_BEGIN_ARGS( Sa2fOptionWindow ) 19 | : _ImportUI(NULL) 20 | , _WidgetWindow() 21 | , _FullPath() 22 | , _MaxWindowHeight(0.0f) 23 | , _MaxWindowWidth(0.0f) 24 | {} 25 | 26 | SLATE_ARGUMENT( Ua2fImportUI*, ImportUI ) 27 | SLATE_ARGUMENT( TSharedPtr, WidgetWindow ) 28 | SLATE_ARGUMENT( FText, FullPath ) 29 | SLATE_ARGUMENT( float, MaxWindowHeight) 30 | SLATE_ARGUMENT(float, MaxWindowWidth) 31 | SLATE_END_ARGS() 32 | 33 | public: 34 | void Construct(const FArguments& InArgs); 35 | virtual bool SupportsKeyboardFocus() const override { return true; } 36 | 37 | FReply OnImport(); 38 | 39 | FReply OnImportAll() 40 | { 41 | bShouldImportAll = true; 42 | return OnImport(); 43 | } 44 | 45 | FReply OnCancel(); 46 | 47 | virtual FReply OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) override; 48 | 49 | bool ShouldImport() const; 50 | 51 | bool ShouldImportAll() const 52 | { 53 | return bShouldImportAll; 54 | } 55 | 56 | Sa2fOptionWindow() 57 | : ImportUI(NULL) 58 | , bShouldImport(false) 59 | , bShouldImportAll(false) 60 | {} 61 | 62 | private: 63 | 64 | bool CanImport() const; 65 | FReply OnResetToDefaultClick() const; 66 | FText GetImportTypeDisplayText() const; 67 | 68 | Ua2fImportUI* ImportUI; 69 | TSharedPtr DetailsView; 70 | TWeakPtr< SWindow > WidgetWindow; 71 | TSharedPtr< SButton > ImportButton; 72 | bool bShouldImport; 73 | bool bShouldImportAll; 74 | }; 75 | -------------------------------------------------------------------------------- /Source/a2fEditor/Private/a2fOptionWindow.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | #include "a2fOptionWindow.h" 3 | #include "Modules/ModuleManager.h" 4 | #include "Widgets/Layout/SBorder.h" 5 | #include "Widgets/Text/STextBlock.h" 6 | #include "Widgets/Layout/SBox.h" 7 | #include "Widgets/Layout/SUniformGridPanel.h" 8 | #include "Widgets/Input/SButton.h" 9 | #include "EditorStyleSet.h" 10 | #include "Factories/FbxAnimSequenceImportData.h" 11 | #include "IDocumentation.h" 12 | #include "PropertyEditorModule.h" 13 | #include "IDetailsView.h" 14 | 15 | #define LOCTEXT_NAMESPACE "Fa2fEditorModule" 16 | 17 | void Sa2fOptionWindow::Construct(const FArguments& InArgs) 18 | { 19 | ImportUI = InArgs._ImportUI; 20 | WidgetWindow = InArgs._WidgetWindow; 21 | 22 | 23 | check (ImportUI); 24 | 25 | TSharedPtr ImportTypeDisplay; 26 | TSharedPtr FbxHeaderButtons; 27 | TSharedPtr InspectorBox; 28 | this->ChildSlot 29 | [ 30 | SNew(SBox) 31 | .MaxDesiredHeight(InArgs._MaxWindowHeight) 32 | .MaxDesiredWidth(InArgs._MaxWindowWidth) 33 | [ 34 | SNew(SVerticalBox) 35 | + SVerticalBox::Slot() 36 | .AutoHeight() 37 | .Padding(2) 38 | [ 39 | SAssignNew(ImportTypeDisplay, SBox) 40 | ] 41 | +SVerticalBox::Slot() 42 | .AutoHeight() 43 | .Padding(2) 44 | [ 45 | SNew(SBorder) 46 | .Padding(FMargin(3)) 47 | .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) 48 | [ 49 | SNew(SHorizontalBox) 50 | +SHorizontalBox::Slot() 51 | .AutoWidth() 52 | [ 53 | SNew(STextBlock) 54 | .Font(FEditorStyle::GetFontStyle("CurveEd.LabelFont")) 55 | .Text(LOCTEXT("Import_CurrentFileTitle", "Current Asset: ")) 56 | ] 57 | +SHorizontalBox::Slot() 58 | .Padding(5, 0, 0, 0) 59 | .AutoWidth() 60 | .VAlign(VAlign_Center) 61 | [ 62 | SNew(STextBlock) 63 | .Font(FEditorStyle::GetFontStyle("CurveEd.InfoFont")) 64 | .Text(InArgs._FullPath) 65 | .ToolTipText(InArgs._FullPath) 66 | ] 67 | ] 68 | ] 69 | + SVerticalBox::Slot() 70 | .AutoHeight() 71 | .Padding(2) 72 | [ 73 | SAssignNew(InspectorBox, SBox) 74 | .MaxDesiredHeight(650.0f) 75 | .WidthOverride(400.0f) 76 | ] 77 | + SVerticalBox::Slot() 78 | .AutoHeight() 79 | .HAlign(HAlign_Right) 80 | .Padding(2) 81 | [ 82 | SNew(SUniformGridPanel) 83 | .SlotPadding(2) 84 | + SUniformGridPanel::Slot(0, 0) 85 | [ 86 | IDocumentation::Get()->CreateAnchor(FString("Engine/Content/FBX/ImportOptions")) 87 | ] 88 | + SUniformGridPanel::Slot(1, 0) 89 | [ 90 | SNew(SButton) 91 | .HAlign(HAlign_Center) 92 | .Text(LOCTEXT("FbxOptionWindow_ImportAll", "Import All")) 93 | .ToolTipText(LOCTEXT("FbxOptionWindow_ImportAll_ToolTip", "Import all files with these same settings")) 94 | .IsEnabled(this, &Sa2fOptionWindow::CanImport) 95 | .OnClicked(this, &Sa2fOptionWindow::OnImportAll) 96 | ] 97 | + SUniformGridPanel::Slot(2, 0) 98 | [ 99 | SAssignNew(ImportButton, SButton) 100 | .HAlign(HAlign_Center) 101 | .Text(LOCTEXT("FbxOptionWindow_Import", "Import")) 102 | .IsEnabled(this, &Sa2fOptionWindow::CanImport) 103 | .OnClicked(this, &Sa2fOptionWindow::OnImport) 104 | ] 105 | + SUniformGridPanel::Slot(3, 0) 106 | [ 107 | SNew(SButton) 108 | .HAlign(HAlign_Center) 109 | .Text(LOCTEXT("FbxOptionWindow_Cancel", "Cancel")) 110 | .ToolTipText(LOCTEXT("FbxOptionWindow_Cancel_ToolTip", "Cancels importing this FBX file")) 111 | .OnClicked(this, &Sa2fOptionWindow::OnCancel) 112 | ] 113 | ] 114 | ] 115 | ]; 116 | 117 | FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked("PropertyEditor"); 118 | FDetailsViewArgs DetailsViewArgs; 119 | DetailsViewArgs.bAllowSearch = false; 120 | DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; 121 | DetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs); 122 | 123 | InspectorBox->SetContent(DetailsView->AsShared()); 124 | 125 | ImportTypeDisplay->SetContent( 126 | SNew(SBorder) 127 | .Padding(FMargin(3)) 128 | .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) 129 | [ 130 | SNew(SHorizontalBox) 131 | + SHorizontalBox::Slot() 132 | .VAlign(VAlign_Center) 133 | [ 134 | SNew(STextBlock) 135 | .Text(this, &Sa2fOptionWindow::GetImportTypeDisplayText) 136 | ] 137 | + SHorizontalBox::Slot() 138 | [ 139 | SNew(SBox) 140 | .HAlign(HAlign_Right) 141 | [ 142 | SAssignNew(FbxHeaderButtons, SHorizontalBox) 143 | + SHorizontalBox::Slot() 144 | .AutoWidth() 145 | .Padding(FMargin(2.0f, 0.0f)) 146 | [ 147 | SNew(SButton) 148 | .Text(LOCTEXT("FbxOptionWindow_ResetOptions", "Reset to Default")) 149 | .OnClicked(this, &Sa2fOptionWindow::OnResetToDefaultClick) 150 | ] 151 | ] 152 | ] 153 | ] 154 | ); 155 | 156 | DetailsView->SetObject(ImportUI); 157 | } 158 | 159 | FReply Sa2fOptionWindow::OnImport() 160 | { 161 | bShouldImport = true; 162 | if ( WidgetWindow.IsValid() ) 163 | { 164 | WidgetWindow.Pin()->RequestDestroyWindow(); 165 | } 166 | return FReply::Handled(); 167 | } 168 | 169 | FReply Sa2fOptionWindow::OnCancel() 170 | { 171 | bShouldImport = false; 172 | bShouldImportAll = false; 173 | if ( WidgetWindow.IsValid() ) 174 | { 175 | WidgetWindow.Pin()->RequestDestroyWindow(); 176 | } 177 | return FReply::Handled(); 178 | } 179 | 180 | FReply Sa2fOptionWindow::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) 181 | { 182 | if( InKeyEvent.GetKey() == EKeys::Escape ) 183 | { 184 | return OnCancel(); 185 | } 186 | 187 | return FReply::Unhandled(); 188 | } 189 | 190 | bool Sa2fOptionWindow::ShouldImport() const 191 | { 192 | return bShouldImport; 193 | } 194 | 195 | FReply Sa2fOptionWindow::OnResetToDefaultClick() const 196 | { 197 | ImportUI->ResetToDefault(); 198 | //Refresh the view to make sure the custom UI are updating correctly 199 | DetailsView->SetObject(ImportUI, true); 200 | return FReply::Handled(); 201 | } 202 | 203 | FText Sa2fOptionWindow::GetImportTypeDisplayText() const 204 | { 205 | return ImportUI->bIsReimport ? LOCTEXT("FbxOptionWindow_ReImportTypeAnim", "Reimport Animation") : LOCTEXT("FbxOptionWindow_ImportTypeAnim", "Import Animation"); 206 | } 207 | 208 | bool Sa2fOptionWindow::CanImport() const 209 | { 210 | // do test to see if we are ready to import 211 | if (ImportUI->Skeleton == NULL) 212 | { 213 | return false; 214 | } 215 | 216 | //if (ImportUI->AnimSequenceImportData->AnimationLength == FBXALIT_SetRange) 217 | //{ 218 | // if (ImportUI->AnimSequenceImportData->FrameImportRange.Min > ImportUI->AnimSequenceImportData->FrameImportRange.Max) 219 | // { 220 | // return false; 221 | // } 222 | //} 223 | 224 | return true; 225 | } 226 | 227 | #undef LOCTEXT_NAMESPACE 228 | -------------------------------------------------------------------------------- /Source/a2fEditor/Private/a2fFactory.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Spitfire Interactive Pty Ltd. All Rights Reserved. 2 | 3 | #include "a2fFactory.h" 4 | 5 | #include "a2fAssetImportData.h" 6 | #include "a2fImportUI.h" 7 | #include "a2fOptionWindow.h" 8 | #include "AnimationUtils.h" 9 | #include "EditorFramework/AssetImportData.h" 10 | #include "Interfaces/IMainFrameModule.h" 11 | #include "Misc/FileHelper.h" 12 | #include "Serialization/JsonSerializer.h" 13 | #include "HAL/PlatformApplicationMisc.h" 14 | 15 | 16 | Ua2fFactory::Ua2fFactory(const FObjectInitializer& ObjectInitializer) 17 | : Super(ObjectInitializer) 18 | { 19 | bCreateNew = false; 20 | bEditorImport = true; 21 | bText = true; 22 | 23 | bShowOption = true; 24 | bOperationCanceled = false; 25 | 26 | ImportUI = nullptr; 27 | 28 | SupportedClass = UAnimSequence::StaticClass(); 29 | Formats.Add("json;JavaScript Object Notation"); 30 | } 31 | 32 | void Ua2fFactory::PostInitProperties() 33 | { 34 | ImportUI = NewObject(this, NAME_None, RF_NoFlags); 35 | 36 | Super::PostInitProperties(); 37 | } 38 | 39 | bool Ua2fFactory::FactoryCanImport(const FString& Filename) 40 | { 41 | return true; 42 | } 43 | 44 | inline UObject* Ua2fFactory::FactoryCreateText(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, 45 | UObject* Context, const TCHAR* Type, const TCHAR*& Buffer, const TCHAR* BufferEnd, FFeedbackContext* Warn, 46 | bool& bOutOperationCanceled) 47 | { 48 | 49 | //We are not re-importing 50 | ImportUI->bIsReimport = false; 51 | //ImportUI->ReimportMesh = nullptr; 52 | //ImportUI->bAllowContentTypeImport = true; 53 | 54 | // Show the import dialog only when not in a "yes to all" state or when automating import 55 | bool bIsAutomated = IsAutomatedImport(); 56 | bool bShowImportDialog = bShowOption && !bIsAutomated; 57 | bool bImportAll = false; 58 | 59 | //Only try and read the old data on individual imports 60 | if(bShowImportDialog) 61 | { 62 | if (const UAnimSequence* ExistingAnimSequence = FindObject(InParent, *InName.ToString())) 63 | { 64 | ImportUI->Skeleton = ExistingAnimSequence->GetSkeleton(); 65 | ImportUI->BoneCompressionSettings = ExistingAnimSequence->BoneCompressionSettings; 66 | ImportUI->CurveCompressionSettings = ExistingAnimSequence->CurveCompressionSettings; 67 | 68 | if (Ua2fAssetImportData* ExistingImportData = Cast(ExistingAnimSequence->AssetImportData)) 69 | { 70 | ImportUI->AnimSequenceImportData = ExistingImportData; 71 | } 72 | } 73 | } 74 | 75 | 76 | 77 | if(bShowImportDialog) 78 | { 79 | TSharedPtr ParentWindow; 80 | 81 | if (FModuleManager::Get().IsModuleLoaded("MainFrame")) 82 | { 83 | IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked("MainFrame"); 84 | ParentWindow = MainFrame.GetParentWindow(); 85 | } 86 | 87 | // Compute centered window position based on max window size, which include when all categories are expanded 88 | const float ImportWindowWidth = 410.0f; 89 | const float ImportWindowHeight = 750.0f; 90 | FVector2D ImportWindowSize = FVector2D(ImportWindowWidth, ImportWindowHeight); // Max window size it can get based on current slate 91 | 92 | 93 | const FSlateRect WorkAreaRect = FSlateApplicationBase::Get().GetPreferredWorkArea(); 94 | const FVector2D DisplayTopLeft(WorkAreaRect.Left, WorkAreaRect.Top); 95 | const FVector2D DisplaySize(WorkAreaRect.Right - WorkAreaRect.Left, WorkAreaRect.Bottom - WorkAreaRect.Top); 96 | 97 | const float ScaleFactor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(DisplayTopLeft.X, DisplayTopLeft.Y); 98 | ImportWindowSize *= ScaleFactor; 99 | 100 | const FVector2D WindowPosition = (DisplayTopLeft + (DisplaySize - ImportWindowSize) / 2.0f) / ScaleFactor; 101 | 102 | TSharedRef Window = SNew(SWindow) 103 | .Title(NSLOCTEXT("UnrealEd", "a2fImportOpionsTitle", "a2f Import Options")) 104 | .SizingRule(ESizingRule::Autosized) 105 | .AutoCenter(EAutoCenter::None) 106 | .ClientSize(ImportWindowSize) 107 | .ScreenPosition(WindowPosition); 108 | 109 | TSharedPtr A2FOptionWindow; 110 | Window->SetContent 111 | ( 112 | SAssignNew(A2FOptionWindow, Sa2fOptionWindow) 113 | .ImportUI(ImportUI) 114 | .WidgetWindow(Window) 115 | .FullPath(FText::FromString(InParent->GetPathName())) 116 | .MaxWindowHeight(ImportWindowHeight) 117 | .MaxWindowWidth(ImportWindowWidth) 118 | ); 119 | 120 | FSlateApplication::Get().AddModalWindow(Window, ParentWindow, false); 121 | 122 | bImportAll = A2FOptionWindow->ShouldImportAll(); 123 | 124 | bOperationCanceled |= !A2FOptionWindow->ShouldImport(); 125 | } 126 | 127 | if(bOperationCanceled) 128 | { 129 | bOutOperationCanceled = true; 130 | 131 | return nullptr; 132 | } 133 | 134 | if(bImportAll) 135 | { 136 | // If the user chose to import all, we don't show the dialog again and use the same settings for each object until importing another set of files 137 | bShowOption = false; 138 | } 139 | 140 | UAnimSequence* AnimSequence = CastChecked(CreateOrOverwriteAsset(InClass, InParent, InName, Flags)); 141 | 142 | if( AnimSequence == nullptr) 143 | { 144 | return nullptr; 145 | } 146 | 147 | USkeleton* Skeleton = ImportUI->Skeleton; 148 | 149 | AnimSequence->SetSkeleton(Skeleton); 150 | 151 | Ua2fAssetImportData* ImportData = Cast(AnimSequence->AssetImportData); 152 | if (!ImportData) 153 | { 154 | ImportData = NewObject(AnimSequence, NAME_None, RF_NoFlags, ImportUI->AnimSequenceImportData); 155 | 156 | // Try to preserve the source file data if possible 157 | if (AnimSequence->AssetImportData != nullptr) 158 | { 159 | ImportData->SourceData = AnimSequence->AssetImportData->SourceData; 160 | } 161 | 162 | AnimSequence->AssetImportData = ImportData; 163 | } 164 | 165 | AnimSequence->AssetImportData->AddFileName(UFactory::GetCurrentFilename(), 0); 166 | 167 | TSharedPtr JsonObject = MakeShareable(new FJsonObject()); 168 | const TSharedRef> JsonReader = TJsonReaderFactory<>::Create(Buffer); 169 | 170 | if (!(JsonObject.IsValid() && FJsonSerializer::Deserialize(JsonReader, JsonObject))) 171 | { 172 | return nullptr; 173 | } 174 | 175 | int32 NumPoses; 176 | JsonObject->TryGetNumberField(TEXT("numPoses"), NumPoses); 177 | int32 NumFrames; 178 | JsonObject->TryGetNumberField(TEXT("numFrames"), NumFrames); 179 | TArray CurveNames; 180 | JsonObject->TryGetStringArrayField(TEXT("facsNames"), CurveNames); 181 | 182 | const TArray>* WeightMat; 183 | JsonObject->TryGetArrayField(TEXT("weightMat"), WeightMat); 184 | 185 | const int32 FPS = ImportUI->AnimSequenceImportData->FrameRate; 186 | 187 | if (CurveNames.Num() == 0 || WeightMat == nullptr) 188 | { 189 | return nullptr; 190 | } 191 | 192 | bool bIsAdditiveAnim = ImportUI->AnimSequenceImportData->AdditiveAnimType == AAT_LocalSpaceBase || ImportUI->AnimSequenceImportData->AdditiveAnimType == AAT_RotationOffsetMeshSpace; 193 | 194 | const int32 Frames = WeightMat->Num(); 195 | 196 | TSet CurvesToStrip; 197 | ImportUI->AnimSequenceImportData->GetCurvesToStrip(CurvesToStrip); 198 | 199 | for(int32 CurveIndex = 0; CurveIndex < CurveNames.Num(); ++CurveIndex) 200 | { 201 | const FString& CurveName = CurveNames[CurveIndex]; 202 | 203 | if(CurvesToStrip.Contains(CurveName)) 204 | { 205 | continue; 206 | } 207 | 208 | const USkeleton::AnimCurveUID CurveUID = Skeleton->GetUIDByName(USkeleton::AnimCurveMappingName, *CurveName); 209 | 210 | if (CurveUID == SmartName::MaxUID) 211 | { 212 | continue; 213 | } 214 | 215 | TArray Keys; 216 | 217 | Keys.Reserve(Frames); 218 | 219 | float WeightMin = FLT_MAX; 220 | float WeightMax = FLT_MIN; 221 | 222 | for (int32 FrameIndex = 0; FrameIndex < Frames; ++FrameIndex) 223 | { 224 | const TSharedPtr& WeightRow = (*WeightMat)[FrameIndex]; 225 | 226 | const float Time = static_cast(FrameIndex) / FPS; 227 | 228 | const TArray>& WeightValues = WeightRow->AsArray(); 229 | 230 | const float Weight = WeightValues[CurveIndex]->AsNumber(); 231 | 232 | WeightMin = FMath::Min(WeightMin, Weight); 233 | WeightMax = FMath::Max(WeightMax, Weight); 234 | 235 | Keys.Add(FRichCurveKey(Time, Weight)); 236 | } 237 | 238 | //If the anim is additive and all the values are zero, don't bother adding a track 239 | if(bIsAdditiveAnim && FMath::IsNearlyEqual(WeightMin,0.0f) && FMath::IsNearlyEqual(WeightMax, 0.0f)) 240 | { 241 | continue; 242 | } 243 | 244 | FSmartName SmartName; 245 | Skeleton->GetSmartNameByUID(USkeleton::AnimCurveMappingName, CurveUID, SmartName); 246 | AnimSequence->RawCurveData.AddCurveData(SmartName); 247 | 248 | FFloatCurve* FloatCurveData = static_cast(AnimSequence->RawCurveData.GetCurveData(CurveUID)); 249 | 250 | FloatCurveData->FloatCurve.SetKeys(Keys); 251 | } 252 | 253 | 254 | for (FCurveDrivenBoneTransform &CurveDrivenBoneTransform : ImportUI->AnimSequenceImportData->CurveDrivenBoneTransforms) 255 | { 256 | FRawAnimSequenceTrack RawTrack; 257 | RawTrack.PosKeys.Empty(); 258 | RawTrack.RotKeys.Empty(); 259 | RawTrack.ScaleKeys.Empty(); 260 | 261 | TArray CurveIndices; 262 | 263 | for (const FCurveDrivenTransform &CurveDrivenTransform : CurveDrivenBoneTransform.CurveDrivenTransforms) 264 | { 265 | CurveIndices.Add(CurveNames.Find(CurveDrivenTransform.Curve)); 266 | } 267 | 268 | const FReferenceSkeleton &ReferenceSkeleton = Skeleton->GetReferenceSkeleton(); 269 | const TArray &RawRefBonePose = ReferenceSkeleton.GetRawRefBonePose(); 270 | 271 | int32 RawBoneIndex = ReferenceSkeleton.FindRawBoneIndex(CurveDrivenBoneTransform.Bone); 272 | 273 | FTransform RawBonePose = RawBoneIndex != INDEX_NONE ? RawRefBonePose[RawBoneIndex] : FTransform::Identity; 274 | 275 | for (int32 FrameIndex = 0; FrameIndex < Frames; ++FrameIndex) 276 | { 277 | FTransform LocalTransform; 278 | 279 | const TSharedPtr& WeightRow = (*WeightMat)[FrameIndex]; 280 | const TArray>& WeightValues = WeightRow->AsArray(); 281 | 282 | for (int32 CurveDrivenIndex = 0; CurveDrivenIndex < CurveDrivenBoneTransform.CurveDrivenTransforms.Num(); ++CurveDrivenIndex) 283 | { 284 | int32 CurveIndex = CurveIndices[CurveDrivenIndex]; 285 | 286 | if(CurveIndex == INDEX_NONE) 287 | { 288 | continue; 289 | } 290 | 291 | FCurveDrivenTransform &CurveDrivenTransform = CurveDrivenBoneTransform.CurveDrivenTransforms[CurveDrivenIndex]; 292 | 293 | FTransform::BlendFromIdentityAndAccumulate(LocalTransform, CurveDrivenTransform.Transform, ScalarRegister(WeightValues[CurveIndex]->AsNumber())); 294 | } 295 | 296 | FTransform CombinedTransform = RawBonePose* LocalTransform; 297 | 298 | RawTrack.ScaleKeys.Add(CombinedTransform.GetScale3D()); 299 | RawTrack.PosKeys.Add(CombinedTransform.GetTranslation()); 300 | RawTrack.RotKeys.Add(CombinedTransform.GetRotation()); 301 | } 302 | 303 | AnimSequence->AddNewRawTrack(CurveDrivenBoneTransform.Bone, &RawTrack); 304 | } 305 | 306 | 307 | AnimSequence->SetRawNumberOfFrame(Frames); 308 | AnimSequence->SequenceLength = static_cast(FMath::Max(Frames - 1, 1)) / FPS; 309 | AnimSequence->ImportFileFramerate = FPS; 310 | 311 | AnimSequence->BoneCompressionSettings = ImportUI->BoneCompressionSettings; 312 | AnimSequence->CurveCompressionSettings = ImportUI->CurveCompressionSettings; 313 | 314 | AnimSequence->AdditiveAnimType = ImportUI->AnimSequenceImportData->AdditiveAnimType; 315 | 316 | return AnimSequence; 317 | } 318 | 319 | void Ua2fFactory::CleanUp() 320 | { 321 | bShowOption = true; 322 | bOperationCanceled = false; 323 | 324 | Super::CleanUp(); 325 | } 326 | 327 | int32 Ua2fFactory::GetPriority() const 328 | { 329 | return INT32_MAX; 330 | } 331 | 332 | bool Ua2fFactory::CanReimport(UObject* Obj, TArray& OutFilenames) 333 | { 334 | if(const UAnimSequence* AnimSequence = Cast(Obj)) 335 | { 336 | if(const UAssetImportData* AssetImportData = AnimSequence->AssetImportData) 337 | { 338 | AssetImportData->ExtractFilenames(OutFilenames); 339 | return true; 340 | } 341 | } 342 | 343 | return false; 344 | } 345 | 346 | void Ua2fFactory::SetReimportPaths(UObject* Obj, const TArray& NewReimportPaths) 347 | { 348 | if (const UAnimSequence* AnimSequence = Cast(Obj)) 349 | { 350 | if(NewReimportPaths.Num() == 1) 351 | { 352 | if(UAssetImportData* AssetImportData = AnimSequence->AssetImportData) 353 | { 354 | AssetImportData->UpdateFilenameOnly(NewReimportPaths[0]); 355 | } 356 | } 357 | } 358 | } 359 | 360 | EReimportResult::Type Ua2fFactory::Reimport(UObject* Obj) 361 | { 362 | const UAnimSequence* AnimSequence = Cast(Obj); 363 | 364 | if (AnimSequence == nullptr) 365 | { 366 | return EReimportResult::Failed; 367 | } 368 | 369 | const FString ResolvedSourceFilePath = AnimSequence->AssetImportData->GetFirstFilename(); 370 | 371 | if (ResolvedSourceFilePath.IsEmpty()) 372 | { 373 | return EReimportResult::Failed; 374 | } 375 | 376 | if (IFileManager::Get().FileSize(*ResolvedSourceFilePath) == INDEX_NONE) 377 | { 378 | return EReimportResult::Failed; 379 | } 380 | 381 | bool bOutCanceled = false; 382 | if (ImportObject(AnimSequence->GetClass(), AnimSequence->GetOuter(), *AnimSequence->GetName(), RF_Public | RF_Standalone, ResolvedSourceFilePath, nullptr, bOutCanceled)) 383 | { 384 | return EReimportResult::Succeeded; 385 | } 386 | 387 | if (bOutCanceled) 388 | { 389 | return EReimportResult::Cancelled; 390 | } 391 | 392 | return EReimportResult::Failed; 393 | } 394 | --------------------------------------------------------------------------------