// Copyright Epic Games, Inc. All Rights Reserved. #include "SkinWeightsUtilities.h" #include "LODUtilities.h" #include "Modules/ModuleManager.h" #include "Components/SkinnedMeshComponent.h" #include "Animation/DebugSkelMeshComponent.h" #include "Rendering/SkeletalMeshModel.h" #include "Rendering/SkeletalMeshLODModel.h" #include "Engine/SkeletalMesh.h" #include "Engine/SkinnedAssetCommon.h" #include "EditorFramework/AssetImportData.h" #include "MeshUtilities.h" #include "IAssetTools.h" #include "AssetToolsModule.h" #include "Factories/FbxFactory.h" #include "Factories/FbxAnimSequenceImportData.h" #include "Factories/FbxSkeletalMeshImportData.h" #include "Factories/FbxStaticMeshImportData.h" #include "Factories/FbxTextureImportData.h" #include "Factories/FbxImportUI.h" #include "AssetRegistry/AssetRegistryModule.h" #include "ObjectTools.h" #include "AssetImportTask.h" #include "FbxImporter.h" #include "ScopedTransaction.h" #include "ComponentReregisterContext.h" #include "Animation/SkinWeightProfile.h" #include "IDesktopPlatform.h" #include "DesktopPlatformModule.h" #include "EditorDirectories.h" #include "Framework/Application/SlateApplication.h" #include "InterchangeAssetImportData.h" #include "InterchangeFilePickerBase.h" #include "InterchangePipelineBase.h" #include "InterchangeManager.h" #include "InterchangeProjectSettings.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "Misc/CoreMisc.h" DEFINE_LOG_CATEGORY_STATIC(LogSkinWeightsUtilities, Log, All); bool FSkinWeightsUtilities::ImportAlternateSkinWeight(USkeletalMesh* SkeletalMesh, const FString& Path, int32 TargetLODIndex, const FName& ProfileName, const bool bIsReimport) { check(SkeletalMesh); FSkeletalMeshLODInfo* LODInfo = SkeletalMesh->GetLODInfo(TargetLODIndex); if (!LODInfo) { UE_LOG(LogSkinWeightsUtilities, Error, TEXT("Cannot import Skin Weight Profile. No valid LOD info.")); return false; } if (LODInfo->bHasBeenSimplified && LODInfo->ReductionSettings.BaseLOD != TargetLODIndex) { //We cannot import alternate skin weights profile for a generated LOD UE_LOG(LogSkinWeightsUtilities, Error, TEXT("Cannot import Skin Weight Profile for a generated LOD. Skin weight profile are transfert from the source LOD during the simplification process.")); return false; } FString AbsoluteFilePath = UAssetImportData::ResolveImportFilename(Path, SkeletalMesh->GetOutermost()); if (!FPaths::FileExists(AbsoluteFilePath)) { UE_LOG(LogSkinWeightsUtilities, Error, TEXT("Path containing Skin Weight Profile data does not exist (%s)."), *Path); return false; } FString Action = bIsReimport ? TEXT("Reimport") : TEXT("Import"); UE_ASSET_LOG(LogSkinWeightsUtilities, Display, SkeletalMesh, TEXT("%s Alternate skin weight Begin [LodIndex: %d] [Profile: %s] [file: %s]."), *Action, TargetLODIndex, *ProfileName.ToString(), *Path); FScopedSuspendAlternateSkinWeightPreview ScopedSuspendAlternateSkinnWeightPreview(SkeletalMesh); FScopedSkeletalMeshPostEditChange ScopePostEditChange(SkeletalMesh); const FString FileExtension = FPaths::GetExtension(AbsoluteFilePath); FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); FString ImportAssetPath = TEXT("/Engine/TempEditor/SkeletalMeshTool"); //Empty the temporary path FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); auto DeletePathAssets = [&AssetRegistryModule, &ImportAssetPath]() { TArray AssetsToDelete; AssetRegistryModule.Get().GetAssetsByPath(FName(*ImportAssetPath), AssetsToDelete, true); for (FAssetData AssetData : AssetsToDelete) { UObject* ObjToDelete = AssetData.GetAsset(); if (ObjToDelete) { //Avoid temporary package to be saved UPackage* Package = ObjToDelete->GetOutermost(); Package->SetDirtyFlag(false); //Avoid gc, use keep flags ObjToDelete->ClearFlags(RF_Standalone); ObjToDelete->ClearInternalFlags(EInternalObjectFlags::Async); //Make the object transient to prevent saving ObjToDelete->SetFlags(RF_Transient); } } }; DeletePathAssets(); UObject* ImportedObject = nullptr; bool bCreateTransaction = false; UInterchangeManager& InterchangeManager = UInterchangeManager::GetInterchangeManager(); const UInterchangeSourceData* SourceData = InterchangeManager.CreateSourceData(AbsoluteFilePath); const bool bInterchangeCanImportSourceData = InterchangeManager.CanTranslateSourceData(SourceData); if (bInterchangeCanImportSourceData) { UInterchangeAssetImportData* SelectedInterchangeAssetImportData = Cast(SkeletalMesh->GetAssetImportData()); if (!SelectedInterchangeAssetImportData) { //Try to convert the asset import data InterchangeManager.ConvertImportData(SkeletalMesh->GetAssetImportData(), UInterchangeAssetImportData::StaticClass(), reinterpret_cast(&SelectedInterchangeAssetImportData)); } UE::Interchange::FScopedSourceData ScopedSourceData(AbsoluteFilePath); const UInterchangeProjectSettings* InterchangeProjectSettings = GetDefault(); FImportAssetParameters ImportAssetParameters; ImportAssetParameters.bIsAutomated = true; // From the InterchangeManager point of view, this is considered an automated import if (const UClass* GenericPipelineClass = InterchangeProjectSettings->GenericPipelineClass.LoadSynchronous()) { UInterchangePipelineBase* GenericPipeline = nullptr; if (SelectedInterchangeAssetImportData) { for (UObject* PipelineObject : SelectedInterchangeAssetImportData->GetPipelines()) { if (PipelineObject->GetClass()->IsChildOf(GenericPipelineClass)) { if (UInterchangePipelineBase* ImportPipeline = Cast(PipelineObject)) { GenericPipeline = Cast(StaticDuplicateObject(ImportPipeline, GetTransientPackage())); break; } } } } if (!GenericPipeline) { GenericPipeline = NewObject(GetTransientPackage(), GenericPipelineClass); } if (GenericPipeline) { GenericPipeline->ClearFlags(EObjectFlags::RF_Standalone | EObjectFlags::RF_Public); FInterchangePipelineContextParams ContextParams; ContextParams.ContextType = bIsReimport ? EInterchangePipelineContext::AssetAlternateSkinningReimport : EInterchangePipelineContext::AssetAlternateSkinningImport; ContextParams.ImportObjectType = USkeletalMesh::StaticClass(); GenericPipeline->AdjustSettingsForContext(ContextParams); ImportAssetParameters.OverridePipelines.Add(GenericPipeline); } } ImportAssetParameters.DestinationName = FGuid::NewGuid().ToString(EGuidFormats::Digits); //TODO create a pipeline that set all the proper skeletalmesh options (look at the legacy system setup) UE::Interchange::FAssetImportResultRef AssetImportResult = InterchangeManager.ImportAssetAsync(ImportAssetPath, ScopedSourceData.GetSourceData(), ImportAssetParameters); AssetImportResult->WaitUntilDone(); //TODO, do not stall the main thread here, WaitUntilDone will tick taskgraph so the job can complete even if we wait on the game thread. if (USkeletalMesh* ImportedSkeletalMesh = Cast< USkeletalMesh >(AssetImportResult->GetFirstAssetOfClass(USkeletalMesh::StaticClass()))) { ImportedObject = ImportedSkeletalMesh; } } else if(FileExtension.Equals(TEXT("fbx"), ESearchCase::IgnoreCase)) { UFbxSkeletalMeshImportData* OriginalSkeletalMeshImportData = Cast(SkeletalMesh->GetAssetImportData()); if (!OriginalSkeletalMeshImportData) { //Convert the data if its Interchange import data if (UInterchangeAssetImportData* InterchangeAssetImportData = Cast(SkeletalMesh->GetAssetImportData())) { UFbxImportUI* FbxImportUI = nullptr; InterchangeManager.ConvertImportData(InterchangeAssetImportData, UFbxImportUI::StaticClass(), reinterpret_cast(&FbxImportUI)); if (FbxImportUI) { OriginalSkeletalMeshImportData = FbxImportUI->SkeletalMeshImportData; } } if (!OriginalSkeletalMeshImportData) { //This will reset the import data OriginalSkeletalMeshImportData = UFbxSkeletalMeshImportData::GetImportDataForSkeletalMesh(SkeletalMesh, nullptr); } } //Import the alternate fbx into a temporary skeletal mesh using the same import options UFbxFactory* FbxFactory = NewObject(UFbxFactory::StaticClass()); FbxFactory->AddToRoot(); FbxFactory->ImportUI = NewObject(FbxFactory); if (OriginalSkeletalMeshImportData != nullptr) { //Copy the skeletal mesh import data options FbxFactory->ImportUI->SkeletalMeshImportData = DuplicateObject(OriginalSkeletalMeshImportData, FbxFactory); } //Skip the auto detect type on import, the test set a specific value FbxFactory->SetDetectImportTypeOnImport(false); FbxFactory->ImportUI->bImportAsSkeletal = true; FbxFactory->ImportUI->MeshTypeToImport = FBXIT_SkeletalMesh; FbxFactory->ImportUI->bIsReimport = false; FbxFactory->ImportUI->ReimportMesh = nullptr; FbxFactory->ImportUI->bAllowContentTypeImport = true; FbxFactory->ImportUI->bImportAnimations = false; FbxFactory->ImportUI->bAutomatedImportShouldDetectType = false; FbxFactory->ImportUI->bCreatePhysicsAsset = false; FbxFactory->ImportUI->bImportMaterials = false; FbxFactory->ImportUI->bImportTextures = false; FbxFactory->ImportUI->bImportMesh = true; FbxFactory->ImportUI->bImportRigidMesh = false; FbxFactory->ImportUI->bIsObjImport = false; FbxFactory->ImportUI->bOverrideFullName = true; FbxFactory->ImportUI->Skeleton = nullptr; //Force some skeletal mesh import options if (FbxFactory->ImportUI->SkeletalMeshImportData) { FbxFactory->ImportUI->SkeletalMeshImportData->bImportMeshLODs = false; FbxFactory->ImportUI->SkeletalMeshImportData->bImportMorphTargets = false; FbxFactory->ImportUI->SkeletalMeshImportData->bUpdateSkeletonReferencePose = false; FbxFactory->ImportUI->SkeletalMeshImportData->ImportContentType = EFBXImportContentType::FBXICT_All; //We need geo and skinning, so we can match the weights } //Force some material options if (FbxFactory->ImportUI->TextureImportData) { FbxFactory->ImportUI->TextureImportData->MaterialSearchLocation = EMaterialSearchLocation::DoNotSearch; FbxFactory->ImportUI->TextureImportData->BaseMaterialName.Reset(); } TArray ImportFilePaths; ImportFilePaths.Add(AbsoluteFilePath); UAssetImportTask* Task = NewObject(); Task->AddToRoot(); Task->bAutomated = true; Task->bReplaceExisting = true; Task->DestinationPath = ImportAssetPath; Task->bSave = false; Task->DestinationName = FGuid::NewGuid().ToString(EGuidFormats::Digits); Task->Options = FbxFactory->ImportUI; Task->Filename = AbsoluteFilePath; Task->Factory = FbxFactory; FbxFactory->SetAssetImportTask(Task); TArray Tasks; Tasks.Add(Task); AssetToolsModule.Get().ImportAssetTasks(Tasks); for (FString AssetPath : Task->ImportedObjectPaths) { FAssetData AssetData = AssetRegistryModule.Get().GetAssetByObjectPath(FSoftObjectPath(AssetPath)); ImportedObject = AssetData.GetAsset(); if (ImportedObject != nullptr) { break; } } //Factory and task can now be garbage collected Task->RemoveFromRoot(); FbxFactory->RemoveFromRoot(); bCreateTransaction = true; } USkeletalMesh* TmpSkeletalMesh = Cast(ImportedObject); if (TmpSkeletalMesh == nullptr || TmpSkeletalMesh->GetSkeleton() == nullptr) { UE_LOG(LogSkinWeightsUtilities, Error, TEXT("Failed to import Skin Weight Profile from provided file (%s)."), *Path); DeletePathAssets(); return false; } //The LOD index of the source is always 0, const int32 SrcLodIndex = 0; bool bResult = false; if (SkeletalMesh && TmpSkeletalMesh) { if (FSkeletalMeshModel* TargetModel = SkeletalMesh->GetImportedModel()) { if (TargetModel->LODModels.IsValidIndex(TargetLODIndex)) { //Prepare the profile data FSkeletalMeshLODModel& TargetLODModel = TargetModel->LODModels[TargetLODIndex]; // Prepare the profile data FSkinWeightProfileInfo* Profile = SkeletalMesh->GetSkinWeightProfiles().FindByPredicate([ProfileName](FSkinWeightProfileInfo Profile) { return Profile.Name == ProfileName; }); const bool bIsReimportLocal = Profile != nullptr; if (bCreateTransaction) { FText TransactionName = bIsReimportLocal ? NSLOCTEXT("UnrealEd", "UpdateAlternateSkinningWeight", "Update Alternate Skinning Weight") : NSLOCTEXT("UnrealEd", "ImportAlternateSkinningWeight", "Import Alternate Skinning Weight"); FScopedTransaction ScopedTransaction(TransactionName); SkeletalMesh->Modify(); } if (bIsReimportLocal) { // Update source file path FString& StoredPath = Profile->PerLODSourceFiles.FindOrAdd(TargetLODIndex); StoredPath = UAssetImportData::SanitizeImportFilename(AbsoluteFilePath, SkeletalMesh->GetOutermost()); Profile->PerLODSourceFiles.KeySort([](int32 A, int32 B) { return A < B; }); } // Clear profile data before import FImportedSkinWeightProfileData& ProfileData = TargetLODModel.SkinWeightProfiles.FindOrAdd(ProfileName); ProfileData.SkinWeights.Empty(); ProfileData.SourceModelInfluences.Empty(); FImportedSkinWeightProfileData PreviousProfileData = ProfileData; IMeshUtilities::MeshBuildOptions BuildOptions; //Use the Lod info build settings BuildOptions.OverlappingThresholds.ThresholdPosition = LODInfo->BuildSettings.ThresholdPosition; BuildOptions.OverlappingThresholds.ThresholdTangentNormal = LODInfo->BuildSettings.ThresholdTangentNormal; BuildOptions.OverlappingThresholds.ThresholdUV = LODInfo->BuildSettings.ThresholdUV; BuildOptions.OverlappingThresholds.MorphThresholdPosition = LODInfo->BuildSettings.MorphThresholdPosition; BuildOptions.bComputeNormals = LODInfo->BuildSettings.bRecomputeNormals; BuildOptions.bComputeTangents = LODInfo->BuildSettings.bRecomputeTangents; BuildOptions.bUseMikkTSpace = LODInfo->BuildSettings.bUseMikkTSpace; BuildOptions.bComputeWeightedNormals = LODInfo->BuildSettings.bComputeWeightedNormals; // There's currently no import option for this. We could add one if needed. BuildOptions.BoneInfluenceLimit = 0; bResult = FLODUtilities::UpdateAlternateSkinWeights(SkeletalMesh, ProfileName, TmpSkeletalMesh, TargetLODIndex, SrcLodIndex, BuildOptions); if (!bResult) { // Remove invalid profile data due to failed import if (!bIsReimportLocal) { TargetLODModel.SkinWeightProfiles.Remove(ProfileName); } else { // Otherwise restore previous data ProfileData = PreviousProfileData; } } // Only add if it is an initial import and it was successful if (!bIsReimportLocal && bResult) { TArray& SkinWeightProfileInfos = SkeletalMesh->GetSkinWeightProfiles(); for (size_t SkinWeightProfileIndex = 0; SkinWeightProfileIndex < SkinWeightProfileInfos.Num(); SkinWeightProfileIndex++) { FSkinWeightProfileInfo& SkinWeightProfileInfo = SkinWeightProfileInfos[SkinWeightProfileInfos.Num() - 1 - SkinWeightProfileIndex]; if (SkinWeightProfileInfo.Name == ProfileName) { SkinWeightProfileInfo.PerLODSourceFiles.Add(TargetLODIndex, UAssetImportData::SanitizeImportFilename(AbsoluteFilePath, SkeletalMesh->GetOutermost())); Profile = &SkinWeightProfileInfo; break; } } } } } } //Make sure all created objects are gone DeletePathAssets(); return bResult; } bool FSkinWeightsUtilities::ReimportAlternateSkinWeight(USkeletalMesh* SkeletalMesh, int32 TargetLODIndex) { bool bResult = false; const TArray& SkinWeightProfiles = SkeletalMesh->GetSkinWeightProfiles(); if (SkinWeightProfiles.Num() <= 0) { return bResult; } FScopedSuspendAlternateSkinWeightPreview ScopedSuspendAlternateSkinnWeightPreview(SkeletalMesh); FScopedSkeletalMeshPostEditChange ScopePostEditChange(SkeletalMesh); for (int32 ProfileIndex = 0; ProfileIndex < SkinWeightProfiles.Num(); ++ProfileIndex) { const FSkinWeightProfileInfo& ProfileInfo = SkinWeightProfiles[ProfileIndex]; const FString* PathNamePtr = ProfileInfo.PerLODSourceFiles.Find(TargetLODIndex); //Skip profile that do not have data for TargetLODIndex if (!PathNamePtr) { continue; } const FString& PathName = *PathNamePtr; FString AbsoluteFilePath = UAssetImportData::ResolveImportFilename(PathName, SkeletalMesh->GetOutermost()); if (FPaths::FileExists(AbsoluteFilePath)) { bResult |= FSkinWeightsUtilities::ImportAlternateSkinWeight(SkeletalMesh, AbsoluteFilePath, TargetLODIndex, ProfileInfo.Name, true); } else { const FString PickedFileName = FSkinWeightsUtilities::PickSkinWeightPath(TargetLODIndex, SkeletalMesh); if (!PickedFileName.IsEmpty() && FPaths::FileExists(PickedFileName)) { bResult |= FSkinWeightsUtilities::ImportAlternateSkinWeight(SkeletalMesh, PickedFileName, TargetLODIndex, ProfileInfo.Name, true); } } } if (bResult) { FLODUtilities::RegenerateDependentLODs(SkeletalMesh, TargetLODIndex, GetTargetPlatformManagerRef().GetRunningTargetPlatform()); } return bResult; } bool FSkinWeightsUtilities::RemoveSkinnedWeightProfileData(USkeletalMesh* SkeletalMesh, const FName& ProfileName, int32 LODIndex) { check(SkeletalMesh); check(SkeletalMesh->GetImportedModel()); check(SkeletalMesh->GetImportedModel()->LODModels.IsValidIndex(LODIndex)); FSkeletalMeshLODModel& LODModelDest = SkeletalMesh->GetImportedModel()->LODModels[LODIndex]; LODModelDest.SkinWeightProfiles.Remove(ProfileName); FSkeletalMeshImportData ImportDataDest; PRAGMA_DISABLE_DEPRECATION_WARNINGS SkeletalMesh->LoadLODImportedData(LODIndex, ImportDataDest); PRAGMA_ENABLE_DEPRECATION_WARNINGS for (size_t AlternateInfluenceIndex = 0; AlternateInfluenceIndex < ImportDataDest.AlternateInfluenceProfileNames.Num(); AlternateInfluenceIndex++) { if (ImportDataDest.AlternateInfluenceProfileNames[AlternateInfluenceIndex] == ProfileName) { ImportDataDest.AlternateInfluenceProfileNames.RemoveAt(AlternateInfluenceIndex); ImportDataDest.AlternateInfluences.RemoveAt(AlternateInfluenceIndex); break; } } //If we have a LOD info we use the build settings to be sure we are rechunking the LOD with the existing options IMeshUtilities::MeshBuildOptions BuildOptions; if (FSkeletalMeshLODInfo* LODInfo = SkeletalMesh->GetLODInfo(LODIndex)) { BuildOptions.OverlappingThresholds.ThresholdPosition = LODInfo->BuildSettings.ThresholdPosition; BuildOptions.OverlappingThresholds.ThresholdTangentNormal = LODInfo->BuildSettings.ThresholdTangentNormal; BuildOptions.OverlappingThresholds.ThresholdUV = LODInfo->BuildSettings.ThresholdUV; BuildOptions.OverlappingThresholds.MorphThresholdPosition = LODInfo->BuildSettings.MorphThresholdPosition; BuildOptions.bComputeNormals = LODInfo->BuildSettings.bRecomputeNormals; BuildOptions.bComputeTangents = LODInfo->BuildSettings.bRecomputeTangents; BuildOptions.bUseMikkTSpace = LODInfo->BuildSettings.bUseMikkTSpace; BuildOptions.bComputeWeightedNormals = LODInfo->BuildSettings.bComputeWeightedNormals; } BuildOptions.bRemoveDegenerateTriangles = false; BuildOptions.TargetPlatform = GetTargetPlatformManagerRef().GetRunningTargetPlatform(); // There's currently no import option for this. We could add one if needed. BuildOptions.BoneInfluenceLimit = 0; TArray LODPointsDest; TArray LODWedgesDest; TArray LODFacesDest; TArray LODInfluencesDest; TArray LODPointToRawMapDest; ImportDataDest.CopyLODImportData(LODPointsDest, LODWedgesDest, LODFacesDest, LODInfluencesDest, LODPointToRawMapDest); //Build the skeletal mesh asset IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked("MeshUtilities"); TArray WarningMessages; TArray WarningNames; //BaseLOD need to make sure the source data fit with the skeletalmesh materials array before using meshutilities.BuildSkeletalMesh FLODUtilities::AdjustImportDataFaceMaterialIndex(SkeletalMesh->GetMaterials(), ImportDataDest.Materials, LODFacesDest, LODIndex); //Build the destination mesh with the Alternate influences, so the chunking is done properly. const bool bBuildSuccess = MeshUtilities.BuildSkeletalMesh(LODModelDest, SkeletalMesh->GetPathName(), SkeletalMesh->GetRefSkeleton(), LODInfluencesDest, LODWedgesDest, LODFacesDest, LODPointsDest, LODPointToRawMapDest, BuildOptions, &WarningMessages, &WarningNames); FLODUtilities::RegenerateAllImportSkinWeightProfileData(LODModelDest, BuildOptions.BoneInfluenceLimit, BuildOptions.TargetPlatform); //Resave the bulk data with the new or refreshed data PRAGMA_DISABLE_DEPRECATION_WARNINGS SkeletalMesh->SaveLODImportedData(LODIndex, ImportDataDest); PRAGMA_ENABLE_DEPRECATION_WARNINGS return bBuildSuccess; } FString FSkinWeightsUtilities::PickSkinWeightPath(int32 LODIndex, USkeletalMesh* SkeletalMesh) { FString PickedFileName(""); bool bUseInterchangeFramework = UInterchangeManager::IsInterchangeImportEnabled(); const UInterchangeAssetImportData* SelectedInterchangeAssetImportData = Cast(SkeletalMesh->GetAssetImportData()); if (bUseInterchangeFramework && SelectedInterchangeAssetImportData) { const FString& FirstSourceFile = SelectedInterchangeAssetImportData->ScriptGetFirstFilename(); FString DefaultPath = FPaths::GetPath(FirstSourceFile); // Otherwise resort back to last imported directory if (!FPaths::DirectoryExists(DefaultPath)) { DefaultPath = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::GENERIC_IMPORT); } //Ask the user for a file path const UInterchangeProjectSettings* InterchangeProjectSettings = GetDefault(); UInterchangeFilePickerBase* FilePicker = nullptr; //In runtime we do not have any pipeline configurator #if WITH_EDITORONLY_DATA TSoftClassPtr FilePickerClass = InterchangeProjectSettings->FilePickerClass; if (FilePickerClass.IsValid()) { UClass* FilePickerClassLoaded = FilePickerClass.LoadSynchronous(); if (FilePickerClassLoaded) { FilePicker = NewObject(GetTransientPackage(), FilePickerClassLoaded, NAME_None, RF_NoFlags); } } #endif if (FilePicker) { FInterchangeFilePickerParameters Parameters; Parameters.bAllowMultipleFiles = false; Parameters.DefaultPath = DefaultPath; Parameters.Title = FText::Format(NSLOCTEXT("FSkinWeightsUtilities", "PickSkinWeightPath_Title", "Choose a file to import alternate skinning for LOD {0}"), FText::AsNumber(LODIndex)); TArray Filenames; if (FilePicker->ScriptedFilePickerForTranslatorAssetType(EInterchangeTranslatorAssetType::Meshes, Parameters, Filenames)) { ensure(Filenames.Num() == 1); PickedFileName = Filenames[0]; FEditorDirectories::Get().SetLastDirectory(ELastDirectory::GENERIC_IMPORT, FPaths::GetPath(PickedFileName)); } else { // Error } } } else { bool bOpen = false; TArray OpenFilenames; FString ExtensionStr; ExtensionStr += TEXT("FBX files|*.fbx|"); // First, display the file open dialog for selecting the file. IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get(); if (DesktopPlatform) { // Try and retrieve the path containing the original skeletal mesh source data, and set it as default path for the file dialog UFbxSkeletalMeshImportData* ImportData = SkeletalMesh ? Cast(SkeletalMesh->GetAssetImportData()) : nullptr; FString DefaultPath; FString TempString; if (ImportData) { ImportData->GetImportContentFilename(DefaultPath, TempString); DefaultPath = FPaths::GetPath(DefaultPath); } // Otherwise resort back to last FBX directory if(!FPaths::DirectoryExists(DefaultPath)) { DefaultPath = FEditorDirectories::Get().GetLastDirectory(ELastDirectory::FBX); } const FString DialogTitle = TEXT("Pick FBX file containing Skin Weight data for LOD ") + FString::FormatAsNumber(LODIndex); bOpen = DesktopPlatform->OpenFileDialog( FSlateApplication::Get().FindBestParentWindowHandleForDialogs(nullptr), DialogTitle, *DefaultPath, TEXT(""), *ExtensionStr, EFileDialogFlags::None, OpenFilenames ); if (bOpen) { if (OpenFilenames.Num() == 1) { PickedFileName = OpenFilenames[0]; // Set last directory path for FBX files FEditorDirectories::Get().SetLastDirectory(ELastDirectory::FBX, FPaths::GetPath(PickedFileName)); } else { // Error } } } } return PickedFileName; }