Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/SkinWeightsUtilities.cpp
2025-05-18 13:04:45 +08:00

601 lines
24 KiB
C++

// 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<FAssetToolsModule>("AssetTools");
FString ImportAssetPath = TEXT("/Engine/TempEditor/SkeletalMeshTool");
//Empty the temporary path
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
auto DeletePathAssets = [&AssetRegistryModule, &ImportAssetPath]()
{
TArray<FAssetData> 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<UInterchangeAssetImportData>(SkeletalMesh->GetAssetImportData());
if (!SelectedInterchangeAssetImportData)
{
//Try to convert the asset import data
InterchangeManager.ConvertImportData(SkeletalMesh->GetAssetImportData(), UInterchangeAssetImportData::StaticClass(), reinterpret_cast<UObject**>(&SelectedInterchangeAssetImportData));
}
UE::Interchange::FScopedSourceData ScopedSourceData(AbsoluteFilePath);
const UInterchangeProjectSettings* InterchangeProjectSettings = GetDefault<UInterchangeProjectSettings>();
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<UInterchangePipelineBase>(PipelineObject))
{
GenericPipeline = Cast<UInterchangePipelineBase>(StaticDuplicateObject(ImportPipeline, GetTransientPackage()));
break;
}
}
}
}
if (!GenericPipeline)
{
GenericPipeline = NewObject<UInterchangePipelineBase>(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<UFbxSkeletalMeshImportData>(SkeletalMesh->GetAssetImportData());
if (!OriginalSkeletalMeshImportData)
{
//Convert the data if its Interchange import data
if (UInterchangeAssetImportData* InterchangeAssetImportData = Cast<UInterchangeAssetImportData>(SkeletalMesh->GetAssetImportData()))
{
UFbxImportUI* FbxImportUI = nullptr;
InterchangeManager.ConvertImportData(InterchangeAssetImportData, UFbxImportUI::StaticClass(), reinterpret_cast<UObject**>(&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>(UFbxFactory::StaticClass());
FbxFactory->AddToRoot();
FbxFactory->ImportUI = NewObject<UFbxImportUI>(FbxFactory);
if (OriginalSkeletalMeshImportData != nullptr)
{
//Copy the skeletal mesh import data options
FbxFactory->ImportUI->SkeletalMeshImportData = DuplicateObject<UFbxSkeletalMeshImportData>(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<FString> ImportFilePaths;
ImportFilePaths.Add(AbsoluteFilePath);
UAssetImportTask* Task = NewObject<UAssetImportTask>();
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<UAssetImportTask*> 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<USkeletalMesh>(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<FSkinWeightProfileInfo>& 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<FSkinWeightProfileInfo>& 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<FVector3f> LODPointsDest;
TArray<SkeletalMeshImportData::FMeshWedge> LODWedgesDest;
TArray<SkeletalMeshImportData::FMeshFace> LODFacesDest;
TArray<SkeletalMeshImportData::FVertInfluence> LODInfluencesDest;
TArray<int32> LODPointToRawMapDest;
ImportDataDest.CopyLODImportData(LODPointsDest, LODWedgesDest, LODFacesDest, LODInfluencesDest, LODPointToRawMapDest);
//Build the skeletal mesh asset
IMeshUtilities& MeshUtilities = FModuleManager::Get().LoadModuleChecked<IMeshUtilities>("MeshUtilities");
TArray<FText> WarningMessages;
TArray<FName> 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<UInterchangeAssetImportData>(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<UInterchangeProjectSettings>();
UInterchangeFilePickerBase* FilePicker = nullptr;
//In runtime we do not have any pipeline configurator
#if WITH_EDITORONLY_DATA
TSoftClassPtr <UInterchangeFilePickerBase> FilePickerClass = InterchangeProjectSettings->FilePickerClass;
if (FilePickerClass.IsValid())
{
UClass* FilePickerClassLoaded = FilePickerClass.LoadSynchronous();
if (FilePickerClassLoaded)
{
FilePicker = NewObject<UInterchangeFilePickerBase>(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<FString> 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<FString> 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<UFbxSkeletalMeshImportData>(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;
}