// Copyright Epic Games, Inc. All Rights Reserved. #include "Factories/FbxFactory.h" #include "Animation/AnimSequence.h" #include "Animation/Skeleton.h" #include "AssetImportTask.h" #include "AssetRegistry/AssetRegistryModule.h" #include "ContentBrowserModule.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "EditorReimportHandler.h" #include "Engine/SkeletalMesh.h" #include "Engine/SkinnedAssetCommon.h" #include "Engine/StaticMesh.h" #include "Factories/FbxAnimSequenceImportData.h" #include "Factories/FbxImportUI.h" #include "Factories/FbxSkeletalMeshImportData.h" #include "Factories/FbxStaticMeshImportData.h" #include "Factories/FbxTextureImportData.h" #include "Factories/ReimportFbxAnimSequenceFactory.h" #include "Factories/ReimportFbxSkeletalMeshFactory.h" #include "Factories/ReimportFbxStaticMeshFactory.h" #include "FbxImporter.h" #include "HAL/FileManager.h" #include "IContentBrowserSingleton.h" #include "Interfaces/ITargetPlatform.h" #include "Interfaces/ITargetPlatformManagerModule.h" #include "JsonObjectConverter.h" #include "LODUtilities.h" #include "Logging/TokenizedMessage.h" #include "Misc/ConfigCacheIni.h" #include "Misc/CoreMisc.h" #include "Misc/FbxErrors.h" #include "Misc/FeedbackContext.h" #include "Misc/Paths.h" #include "Misc/ScopedSlowTask.h" #include "ObjectTools.h" #include "PackageTools.h" #include "PhysicsEngine/PhysicsAsset.h" #include "StaticMeshResources.h" #include "UObject/UnrealType.h" #define LOCTEXT_NAMESPACE "FBXFactory" UFbxFactory::UFbxFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { SupportedClass = NULL; Formats.Add(TEXT("fbx;FBX meshes and animations")); Formats.Add(TEXT("obj;OBJ Static meshes")); //Formats.Add(TEXT("dae;Collada meshes and animations")); bCreateNew = false; bText = false; bEditorImport = true; bOperationCanceled = false; bDetectImportTypeOnImport = true; } void UFbxFactory::PostInitProperties() { Super::PostInitProperties(); bEditorImport = true; bText = false; ImportUI = NewObject(this, NAME_None, RF_NoFlags); UFbxImportUI::LoadOptions(ImportUI); } bool UFbxFactory::DoesSupportClass(UClass * Class) { return (Class == UStaticMesh::StaticClass() || Class == USkeletalMesh::StaticClass() || Class == UAnimSequence::StaticClass()); } UClass* UFbxFactory::ResolveSupportedClass() { UClass* ImportClass = NULL; if (ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh) { ImportClass = USkeletalMesh::StaticClass(); } else if (ImportUI->MeshTypeToImport == FBXIT_Animation) { ImportClass = UAnimSequence::StaticClass(); } else { ImportClass = UStaticMesh::StaticClass(); } return ImportClass; } bool UFbxFactory::DetectImportType(const FString& InFilename) { UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); int32 ImportType = FFbxImporter->GetImportType(InFilename); if ( ImportType == -1) { FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, LOCTEXT("NoImportTypeDetected", "Can't detect import type. No mesh is found or animation track.")), FFbxErrors::Generic_CannotDetectImportType); FFbxImporter->ReleaseScene(); return false; } else if(!IsAutomatedImport() || ImportUI->bAutomatedImportShouldDetectType) { ImportUI->MeshTypeToImport = EFBXImportType(ImportType); ImportUI->OriginalImportType = ImportUI->MeshTypeToImport; } return true; } UObject* UFbxFactory::ImportANode(UnFbx::FFbxImporter* FbxImporter, TArray VoidNodes, UObject* InParent, FName InName, EObjectFlags Flags, FScopedSlowTask& SlowTask, UObject* InMesh, int LODIndex) { FFormatNamedArguments Args; Args.Add(TEXT("NodeIndex"), SlowTask.CompletedWork + 1); Args.Add(TEXT("ArrayLength"), SlowTask.TotalAmountOfWork); SlowTask.EnterProgressFrame(1, GetImportTaskText(FText::Format(NSLOCTEXT("UnrealEd", "Importingf", "Importing ({NodeIndex} of {ArrayLength})"), Args))); TArray Nodes; for (void* VoidNode : VoidNodes) { Nodes.Add((FbxNode*)VoidNode); } check(Nodes.Num() > 0 && Nodes[0] != nullptr); UObject* CreatedObject = NULL; FName OutputName = FbxImporter->MakeNameForMesh(InName.ToString(), Nodes[0]); { // skip collision models FbxString NodeName(Nodes[0]->GetName()); if ( NodeName.Find("UCX") != -1 || NodeName.Find("MCDCX") != -1 || NodeName.Find("UBX") != -1 || NodeName.Find("USP") != -1 || NodeName.Find("UCP") != -1 ) { return NULL; } CreatedObject = FbxImporter->ImportStaticMeshAsSingle( InParent, Nodes, OutputName, Flags, ImportUI->StaticMeshImportData, Cast(InMesh), LODIndex ); bOperationCanceled |= FbxImporter->GetImportOperationCancelled(); } return CreatedObject; } FText UFbxFactory::GetImportTaskText(const FText& TaskText) const { return bOperationCanceled ? LOCTEXT("CancellingImportingFbxMeshTask", "Cancelling FBX mesh import") : TaskText; } bool UFbxFactory::ConfigureProperties() { bDetectImportTypeOnImport = true; EnableShowOption(); return true; } UObject* UFbxFactory::FactoryCreateFile ( UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, const FString& InFilename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled ) { FScopedSlowTask SlowTask(5, GetImportTaskText(NSLOCTEXT("FbxFactory", "BeginReadFbxFile", "Opening FBX file.")), true); if (Warn->GetScopeStack().Num() == 0) { // We only display this slow task if there is no parent slowtask, because otherwise it is redundant and doesn't display any relevant information on the progress. // It is primarly used to regroup all the smaller import sub-tasks for a smoother progression. SlowTask.MakeDialog(true); } SlowTask.EnterProgressFrame(0); AdditionalImportedObjects.Empty(); FString FileExtension = FPaths::GetExtension(InFilename); const TCHAR* Type = *FileExtension; if (!IFileManager::Get().FileExists(*InFilename)) { UE_LOG(LogFbx, Error, TEXT("Failed to load file '%s'"), *InFilename) return nullptr; } ParseParms(Parms); CA_ASSUME(InParent); if( bOperationCanceled ) { bOutOperationCanceled = true; GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, NULL); return NULL; } GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, Class, InParent, Name, Type); FString ParentPackagePath = (InParent && InParent->GetOutermost()) ? FPackageName::GetLongPackagePath(InParent->GetOutermost()->GetName()) : FString(TEXT("/Game")); UObject* CreatedObject = NULL; //Look if its a re-import, in that case we must call the re-import factory UObject *ExistingObject = nullptr; if (InParent != nullptr) { ExistingObject = StaticFindObject(UObject::StaticClass(), InParent, *(Name.ToString())); if (ExistingObject) { FReimportHandler* ReimportHandler = nullptr; UFactory* ReimportHandlerFactory = nullptr; UAssetImportTask* HandlerOriginalImportTask = nullptr; bool bIsObjectSupported = true; if (Cast(ExistingObject)) { bIsObjectSupported = true; // Using RF_NoFlags because we want to include class default object, which are used by the reimport manager. if (TObjectIterator ReimportFactoryIterator(RF_NoFlags); ReimportFactoryIterator) { HandlerOriginalImportTask = ReimportFactoryIterator->AssetImportTask; ReimportFactoryIterator->AssetImportTask = AssetImportTask; ReimportHandler = *ReimportFactoryIterator; ReimportHandlerFactory = *ReimportFactoryIterator; } } else if (Cast(ExistingObject)) { bIsObjectSupported = true; // Using RF_NoFlags because we want to include class default object, which are used by the reimport manager. if (TObjectIterator ReimportFactoryIterator(RF_NoFlags); ReimportFactoryIterator) { HandlerOriginalImportTask = ReimportFactoryIterator->AssetImportTask; ReimportFactoryIterator->AssetImportTask = AssetImportTask; ReimportHandler = *ReimportFactoryIterator; ReimportHandlerFactory = *ReimportFactoryIterator; } } else if (Cast(ExistingObject)) { bIsObjectSupported = true; // Using RF_NoFlags because we want to include class default object, which are used by the reimport manager. if (TObjectIterator ReimportFactoryIterator(RF_NoFlags); ReimportFactoryIterator) { HandlerOriginalImportTask = ReimportFactoryIterator->AssetImportTask; ReimportFactoryIterator->AssetImportTask = AssetImportTask; ReimportHandler = *ReimportFactoryIterator; ReimportHandlerFactory = *ReimportFactoryIterator; } } if (bIsObjectSupported) { TArray ToReimportObjects; ToReimportObjects.Add(ExistingObject); TArray Filenames; Filenames.Add(UFactory::CurrentFilename); //Set the new fbx source path before starting the re-import FReimportManager::Instance()->UpdateReimportPaths(ExistingObject, Filenames); //Do the re-import and exit const bool bIsAutomated = IsAutomatedImport(); const bool bShowNotification = !bIsAutomated; const int32 SourceFileIndex = INDEX_NONE; const bool bForceNewFile = false; if (ReimportHandler) { const bool bAskForNewFileIfMissing = true; const FString PreferedReimportFile; FReimportManager::Instance()->Reimport(ExistingObject, bAskForNewFileIfMissing, bShowNotification, PreferedReimportFile, ReimportHandler, SourceFileIndex, bForceNewFile, bIsAutomated); if (ReimportHandlerFactory) { ReimportHandlerFactory->AssetImportTask = HandlerOriginalImportTask; } } else { FReimportManager::Instance()->ValidateAllSourceFileAndReimport(ToReimportObjects, bShowNotification, SourceFileIndex, bForceNewFile, bIsAutomated); } return ExistingObject; } } } // logger for all error/warnings UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); UnFbx::FFbxLoggerSetter Logger(FbxImporter); if ( bDetectImportTypeOnImport) { if ( !DetectImportType(UFactory::CurrentFilename) ) { // Failed to read the file info, fail the import GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, NULL); return NULL; } } // this one prints all messages that are stored in FFbxImporter UnFbx::FBXImportOptions* ImportOptions = FbxImporter->GetImportOptions(); if (bShowOption) { //Clean up the options UnFbx::FBXImportOptions::ResetOptions(ImportOptions); } EFBXImportType ForcedImportType = FBXIT_StaticMesh; bool bIsObjFormat = false; if( FString(Type).Equals(TEXT("obj"), ESearchCase::IgnoreCase ) ) { bIsObjFormat = true; } struct FRestoreImportUI { FRestoreImportUI(UFbxFactory* InFbxFactory) : FbxFactory(InFbxFactory) { ensure(FbxFactory->OriginalImportUI == nullptr); FbxFactory->OriginalImportUI = FbxFactory->ImportUI; } ~FRestoreImportUI() { FbxFactory->ImportUI = FbxFactory->OriginalImportUI; FbxFactory->OriginalImportUI = nullptr; } private: UFbxFactory* FbxFactory; }; FRestoreImportUI RestoreImportUI(this); UFbxImportUI* OverrideImportUI = AssetImportTask ? Cast(AssetImportTask->Options) : nullptr; if (OverrideImportUI) { if (AssetImportTask->bAutomated && OverrideImportUI->bAutomatedImportShouldDetectType) { OverrideImportUI->MeshTypeToImport = ImportUI->MeshTypeToImport; OverrideImportUI->OriginalImportType = ImportUI->OriginalImportType; } ImportUI = OverrideImportUI; } else if(AssetImportTask && AssetImportTask->Options) { UE_LOG(LogFbx, Display, TEXT("The options set in the Asset Import Task are not of type UFbxImportUI and will be ignored")); } //We are not re-importing ImportUI->bIsReimport = false; ImportUI->ReimportMesh = nullptr; ImportUI->bAllowContentTypeImport = true; // Show the import dialog only when not in a "yes to all" state or when automating import bool bIsAutomated = IsAutomatedImport(); bool bShowImportDialog = bShowOption && !bIsAutomated; bool bImportAll = false; ImportOptions = GetImportOptions(FbxImporter, ImportUI, bShowImportDialog, bIsAutomated, InParent->GetPathName(), bOperationCanceled, bImportAll, bIsObjFormat, UFactory::CurrentFilename, false, ForcedImportType); bOutOperationCanceled = bOperationCanceled; SlowTask.EnterProgressFrame(4, GetImportTaskText(NSLOCTEXT("FbxFactory", "BeginImportingFbxMeshTask", "Importing FBX mesh"))); if( bImportAll ) { // 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 bShowOption = false; } // Automated importing does not use the same settings and gets its settings straight from the user if(!bIsAutomated) { // For multiple files, use the same settings bDetectImportTypeOnImport = false; } if (ImportOptions) { ImportOptions->bCanShowDialog = !(GIsAutomationTesting || FApp::IsUnattended()); ImportOptions->bIsImportCancelable = true; if (ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh) { if (ImportOptions->bImportAsSkeletalSkinning) { ImportOptions->bImportMaterials = false; ImportOptions->bImportTextures = false; ImportOptions->bImportLOD = false; ImportOptions->bImportSkeletalMeshLODs = false; ImportOptions->bImportAnimations = false; ImportOptions->bImportMorph = false; } else if (ImportOptions->bImportAsSkeletalGeometry) { ImportOptions->bImportAnimations = false; ImportOptions->bUpdateSkeletonReferencePose = false; } } if (!FbxImporter->CanImportClass(UPhysicsAsset::StaticClass())) { ImportOptions->bCreatePhysicsAsset = false; } if ( !FbxImporter->ImportFromFile( *UFactory::CurrentFilename, Type, true ) ) { // Log the error message and fail the import. Warn->Log(ELogVerbosity::Error, FbxImporter->GetErrorMessage() ); } else { // Log the import message and import the mesh. const TCHAR* errorMessage = FbxImporter->GetErrorMessage(); if (errorMessage[0] != '\0') { Warn->Log( errorMessage ); } FbxNode* RootNodeToImport = NULL; RootNodeToImport = FbxImporter->Scene->GetRootNode(); // For animation and static mesh we assume there is at lease one interesting node by default int32 InterestingNodeCount = 1; TArray< TArray* > SkelMeshArray; bool bImportStaticMeshLODs = ImportUI->StaticMeshImportData->bImportMeshLODs; bool bCombineMeshes = ImportUI->StaticMeshImportData->bCombineMeshes; bool bCombineMeshesLOD = false; const bool bCanImportStaticMesh = FbxImporter->CanImportClass(UStaticMesh::StaticClass()); //We can import skeletal mesh only if we can import skeleton or a skeleton was specified in the import options const bool bCanImportSkeletalMesh = FbxImporter->CanImportClass(USkeletalMesh::StaticClass()) && (ImportOptions->SkeletonForAnimation || FbxImporter->CanImportClass(USkeleton::StaticClass())); const bool bCanImportAnimSequence = FbxImporter->CanImportClass(UAnimSequence::StaticClass()); if (bCanImportSkeletalMesh && ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh ) { FbxImporter->FillFbxSkelMeshArrayInScene(RootNodeToImport, SkelMeshArray, false, (ImportOptions->bImportAsSkeletalGeometry || ImportOptions->bImportAsSkeletalSkinning)); InterestingNodeCount = SkelMeshArray.Num(); } else if(bCanImportStaticMesh && ImportUI->MeshTypeToImport == FBXIT_StaticMesh ) { FbxImporter->ApplyTransformSettingsToFbxNode(RootNodeToImport, ImportUI->StaticMeshImportData); if( bCombineMeshes && !bImportStaticMeshLODs ) { // If Combine meshes and dont import mesh LODs, the interesting node count should be 1 so all the meshes are grouped together into one static mesh InterestingNodeCount = 1; } else { // count meshes in lod groups if we dont care about importing LODs bool bCountLODGroupMeshes = !bImportStaticMeshLODs && bCombineMeshes; int32 NumLODGroups = 0; InterestingNodeCount = FbxImporter->GetFbxMeshCount(RootNodeToImport,bCountLODGroupMeshes,NumLODGroups); // if there were LODs in the file, do not combine meshes even if requested if( bImportStaticMeshLODs && bCombineMeshes && NumLODGroups > 0) { bCombineMeshes = false; //Combine all the LOD together and export one mesh with LODs bCombineMeshesLOD = true; } } //Find all collision models, even the one contain under a LOD Group FbxImporter->FillFbxCollisionMeshArray(RootNodeToImport); } if (InterestingNodeCount > 1) { // the option only works when there are only one asset ImportOptions->bUsedAsFullName = false; } const FString Filename( UFactory::CurrentFilename ); if (RootNodeToImport && InterestingNodeCount > 0) { int32 NodeIndex = 0; int32 ImportedMeshCount = 0; if (bCanImportStaticMesh && ImportUI->MeshTypeToImport == FBXIT_StaticMesh ) // static mesh { UStaticMesh* NewStaticMesh = NULL; if (bCombineMeshes) { TArray FbxMeshArray; FbxImporter->FillFbxMeshArray(RootNodeToImport, FbxMeshArray, FbxImporter); if (FbxMeshArray.Num() > 0) { NewStaticMesh = FbxImporter->ImportStaticMeshAsSingle(InParent, FbxMeshArray, Name, Flags, ImportUI->StaticMeshImportData, NULL, 0, nullptr); bOperationCanceled |= SlowTask.ShouldCancel() || FbxImporter->GetImportOperationCancelled(); if (!bOperationCanceled && NewStaticMesh != nullptr) { //Build the staticmesh SlowTask.EnterProgressFrame(1, GetImportTaskText(NSLOCTEXT("FbxFactory", "EndingImportingFbxMeshTask", "Finalizing static mesh import."))); FbxImporter->PostImportStaticMesh(NewStaticMesh, FbxMeshArray); FbxImporter->UpdateStaticMeshImportData(NewStaticMesh, nullptr); } } ImportedMeshCount = NewStaticMesh ? 1 : 0; } else if (bCombineMeshesLOD) { TArray FbxMeshArray; TArray FbxLodGroups; TArray> FbxMeshesLod; FbxImporter->FillFbxMeshAndLODGroupArray(RootNodeToImport, FbxLodGroups, FbxMeshArray); FbxMeshesLod.Add(FbxMeshArray); for (FbxNode* LODGroup : FbxLodGroups) { if (LODGroup->GetNodeAttribute() && LODGroup->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup && LODGroup->GetChildCount() > 0) { for (int32 GroupLodIndex = 0; GroupLodIndex < LODGroup->GetChildCount(); ++GroupLodIndex) { if (GroupLodIndex >= MAX_STATIC_MESH_LODS) { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format( LOCTEXT("ImporterLimits_MaximumStaticMeshLODReach", "Reached the maximum number of LODs for a Static Mesh({0}) - discarding {1} LOD meshes."), FText::AsNumber(MAX_STATIC_MESH_LODS), FText::AsNumber(LODGroup->GetChildCount() - MAX_STATIC_MESH_LODS)) ), FFbxErrors::Generic_Mesh_TooManyLODs); break; } TArray AllNodeInLod; FbxImporter->FindAllLODGroupNode(AllNodeInLod, LODGroup, GroupLodIndex); if (AllNodeInLod.Num() > 0) { if (FbxMeshesLod.Num() <= GroupLodIndex) { FbxMeshesLod.Add(AllNodeInLod); } else { TArray &LODGroupArray = FbxMeshesLod[GroupLodIndex]; for (FbxNode* NodeToAdd : AllNodeInLod) { LODGroupArray.Add(NodeToAdd); } } } } } } FFormatNamedArguments Args; Args.Add(TEXT("NodeIndex"), 1); Args.Add(TEXT("ArrayLength"), FbxMeshesLod.Num()); FText SlowtaskText = NSLOCTEXT("UnrealEd", "Importingf", "Importing ({NodeIndex} of {ArrayLength})"); FScopedSlowTask ImportStaticMeshSlowTask(FbxMeshesLod.Num(), GetImportTaskText(FText::Format(SlowtaskText, Args))); ImportStaticMeshSlowTask.MakeDialog(); //Import the LOD root if (FbxMeshesLod.Num() > 0) { ImportStaticMeshSlowTask.EnterProgressFrame(1, GetImportTaskText(FText::Format(SlowtaskText, Args))); TArray &LODMeshesArray = FbxMeshesLod[0]; NewStaticMesh = FbxImporter->ImportStaticMeshAsSingle(InParent, LODMeshesArray, Name, Flags, ImportUI->StaticMeshImportData, NULL, 0); bOperationCanceled |= FbxImporter->GetImportOperationCancelled(); } //Import all LODs for (int32 LODIndex = 1; LODIndex < FbxMeshesLod.Num() && !bOperationCanceled; ++LODIndex) { TArray &LODMeshesArray = FbxMeshesLod[LODIndex]; Args[TEXT("NodeIndex")] = LODIndex + 1; ImportStaticMeshSlowTask.EnterProgressFrame(1, GetImportTaskText(FText::Format(SlowtaskText, Args))); if (LODMeshesArray[0]->GetMesh() == nullptr) { FbxImporter->AddStaticMeshSourceModelGeneratedLOD(NewStaticMesh, LODIndex); } else { UStaticMesh* ImportedStaticMesh = FbxImporter->ImportStaticMeshAsSingle(InParent, LODMeshesArray, Name, Flags, ImportUI->StaticMeshImportData, NewStaticMesh, LODIndex); if (ImportedStaticMesh) { check(ImportedStaticMesh == NewStaticMesh); if (NewStaticMesh && NewStaticMesh->IsSourceModelValid(LODIndex)) { NewStaticMesh->GetSourceModel(LODIndex).bImportWithBaseMesh = true; } } else { FbxImporter->AddStaticMeshSourceModelGeneratedLOD(NewStaticMesh, LODIndex); } } bOperationCanceled |= FbxImporter->GetImportOperationCancelled() || SlowTask.ShouldCancel(); } if (!bOperationCanceled && NewStaticMesh) { FbxImporter->PostImportStaticMesh(NewStaticMesh, FbxMeshesLod[0]); FbxImporter->UpdateStaticMeshImportData(NewStaticMesh, nullptr); } } else // !bCombineMeshes && !bCombineMeshesLOD { FFormatNamedArguments Args; Args.Add(TEXT("NodeIndex"), 1); Args.Add(TEXT("ArrayLength"), InterestingNodeCount); FScopedSlowTask ImportNodeSlowTask(InterestingNodeCount, GetImportTaskText(FText::Format(NSLOCTEXT("UnrealEd", "Importingf", "Importing ({NodeIndex} of {ArrayLength})"), Args))); ImportNodeSlowTask.MakeDialog(true); TArray AllNewAssets; UObject* Object = RecursiveImportNode(FbxImporter, RootNodeToImport, InParent, Name, Flags, ImportNodeSlowTask, AllNewAssets); bOperationCanceled |= SlowTask.ShouldCancel(); NewStaticMesh = Cast( Object ); // Make sure to notify the asset registry of all assets created other than the one returned, which will notify the asset registry automatically. for ( auto AssetIt = AllNewAssets.CreateConstIterator(); !bOperationCanceled && AssetIt; ++AssetIt ) { UObject* Asset = *AssetIt; if ( Asset != NewStaticMesh ) { FAssetRegistryModule::AssetCreated(Asset); Asset->MarkPackageDirty(); //Make sure the build is up to date with the latest section info map Asset->PostEditChange(); AdditionalImportedObjects.Add(Asset); } } ImportedMeshCount = AllNewAssets.Num(); } // Importing static mesh global sockets only if one mesh is imported if(!bOperationCanceled && ImportedMeshCount == 1 && NewStaticMesh) { FbxImporter->ImportStaticMeshGlobalSockets( NewStaticMesh ); } CreatedObject = NewStaticMesh; } else if ( bCanImportSkeletalMesh && ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh )// skeletal mesh { int32 TotalNumNodes = 0; for (int32 i = 0; i < SkelMeshArray.Num() && !bOperationCanceled; i++) { UPackage* Package = (SkelMeshArray.Num() == 1 ? Cast(InParent) : nullptr); USkeletalMesh* BaseSkeletalMesh = nullptr; TArray NodeArray = *SkelMeshArray[i]; TotalNumNodes += NodeArray.Num(); // check if there is LODGroup for this skeletal mesh int32 MaxNumberOfLOD = 1; for (int32 j = 0; j < NodeArray.Num(); j++) { FbxNode* Node = NodeArray[j]; if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { // get max LODgroup level if (MaxNumberOfLOD < Node->GetChildCount()) { MaxNumberOfLOD = Node->GetChildCount(); } } } int32 SuccessfulLodIndex = 0; bool bImportSkeletalMeshLODs = ImportUI->SkeletalMeshImportData->bImportMeshLODs; //The skeletalmesh will be set after we import the LOD 0 since it is not created yet. FScopedSkeletalMeshPostEditChange ScopedPostEditChange(nullptr); for (int32 LODIndex = 0; LODIndex < MaxNumberOfLOD && !bOperationCanceled; LODIndex++) { //We need to know what is the imported lod index when importing the morph targets int32 ImportedSuccessfulLodIndex = INDEX_NONE; if ( !bImportSkeletalMeshLODs && LODIndex > 0) // not import LOD if UI option is OFF { break; } TArray SkelMeshNodeArray; for (int32 j = 0; j < NodeArray.Num(); j++) { FbxNode* Node = NodeArray[j]; if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { TArray NodeInLod; if (Node->GetChildCount() > LODIndex) { FbxImporter->FindAllLODGroupNode(NodeInLod, Node, LODIndex); } else // in less some LODGroups have less level, use the last level { FbxImporter->FindAllLODGroupNode(NodeInLod, Node, Node->GetChildCount() - 1); } for (FbxNode *MeshNode : NodeInLod) { SkelMeshNodeArray.Add(MeshNode); } } else { SkelMeshNodeArray.Add(Node); } } FSkeletalMeshImportData OutData; bool bMapMorphTargetToTimeZero = false; if (LODIndex == 0 && SkelMeshNodeArray.Num() != 0) { FName OutputName = FbxImporter->MakeNameForMesh((Package == nullptr) ? TEXT("None") : Name.ToString(), SkelMeshNodeArray[0]); if (Package) { if (!OutputName.ToString().Equals(FPaths::GetCleanFilename(Package->GetName()), ESearchCase::IgnoreCase)) { //We need to create a new package Package = nullptr; } } if (Package == nullptr) { FString NewPackageName; if (!ParentPackagePath.IsEmpty()) { NewPackageName = ParentPackagePath + TEXT("/") + OutputName.ToString(); } else { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText::Format(LOCTEXT("ImportSkeletalMesh", "Invalid Parent package when importing {0}.\nThe asset will not be imported."), FText::FromName(OutputName))), FFbxErrors::Generic_ImportingNewObjectFailed); NewPackageName = OutputName.ToString(); } NewPackageName = UPackageTools::SanitizePackageName(NewPackageName); Package = CreatePackage(*NewPackageName); Package->FullyLoad(); } UnFbx::FFbxImporter::FImportSkeletalMeshArgs ImportSkeletalMeshArgs; ImportSkeletalMeshArgs.InParent = Package; ImportSkeletalMeshArgs.NodeArray = SkelMeshNodeArray; ImportSkeletalMeshArgs.Name = OutputName; ImportSkeletalMeshArgs.Flags = Flags; ImportSkeletalMeshArgs.TemplateImportData = ImportUI->SkeletalMeshImportData; ImportSkeletalMeshArgs.LodIndex = LODIndex; ImportSkeletalMeshArgs.OutData = &OutData; BaseSkeletalMesh = FbxImporter->ImportSkeletalMesh( ImportSkeletalMeshArgs ); bOperationCanceled |= FbxImporter->GetImportOperationCancelled() || SlowTask.ShouldCancel(); if ( BaseSkeletalMesh && !bOperationCanceled) { //Set the base skeletalmesh to the scoped post edit change variable ScopedPostEditChange.SetSkeletalMesh(BaseSkeletalMesh); if (ImportOptions->bImportAnimations) { // We need to remove all scaling from the root node before we set up animation data. // Othewise some of the global transform calculations will be incorrect. FbxImporter->RemoveTransformSettingsFromFbxNode(RootNodeToImport, ImportUI->SkeletalMeshImportData); FbxImporter->SetupAnimationDataFromMesh(BaseSkeletalMesh, Package, SkelMeshNodeArray, ImportUI->AnimSequenceImportData, OutputName.ToString()); // Reapply the transforms for the rest of the import FbxImporter->ApplyTransformSettingsToFbxNode(RootNodeToImport, ImportUI->SkeletalMeshImportData); } bMapMorphTargetToTimeZero = ImportSkeletalMeshArgs.bMapMorphTargetToTimeZero; ImportedSuccessfulLodIndex = SuccessfulLodIndex; //Increment the LOD index SuccessfulLodIndex++; } } else if (BaseSkeletalMesh && SkelMeshNodeArray[0]->GetMesh() == nullptr) { FSkeletalMeshUpdateContext UpdateContext; UpdateContext.SkeletalMesh = BaseSkeletalMesh; //Add a autogenerated LOD to the BaseSkeletalMesh FSkeletalMeshLODInfo& LODInfo = BaseSkeletalMesh->AddLODInfo(); LODInfo.ReductionSettings.NumOfTrianglesPercentage = FMath::Pow(0.5f, (float)(SuccessfulLodIndex)); LODInfo.ReductionSettings.BaseLOD = 0; LODInfo.bImportWithBaseMesh = true; LODInfo.SourceImportFilename = FString(TEXT("")); FLODUtilities::SimplifySkeletalMeshLOD(UpdateContext, SuccessfulLodIndex, GetTargetPlatformManagerRef().GetRunningTargetPlatform()); ImportedSuccessfulLodIndex = SuccessfulLodIndex; SuccessfulLodIndex++; } else if (BaseSkeletalMesh) // the base skeletal mesh is imported successfully { FName LODObjectName = NAME_None; UnFbx::FFbxImporter::FImportSkeletalMeshArgs ImportSkeletalMeshArgs; ImportSkeletalMeshArgs.InParent = BaseSkeletalMesh->GetOutermost(); ImportSkeletalMeshArgs.NodeArray = SkelMeshNodeArray; ImportSkeletalMeshArgs.Name = LODObjectName; ImportSkeletalMeshArgs.Flags = RF_Transient; ImportSkeletalMeshArgs.TemplateImportData = ImportUI->SkeletalMeshImportData; ImportSkeletalMeshArgs.LodIndex = SuccessfulLodIndex; ImportSkeletalMeshArgs.OutData = &OutData; USkeletalMesh *LODObject = FbxImporter->ImportSkeletalMesh( ImportSkeletalMeshArgs ); bOperationCanceled |= FbxImporter->GetImportOperationCancelled(); if (!bOperationCanceled && FbxImporter->ImportSkeletalMeshLOD(LODObject, BaseSkeletalMesh, SuccessfulLodIndex)) { FSkeletalMeshLODInfo* LODInfo = BaseSkeletalMesh->GetLODInfo(SuccessfulLodIndex); LODInfo->bImportWithBaseMesh = true; LODInfo->SourceImportFilename = FString(TEXT("")); bMapMorphTargetToTimeZero = ImportSkeletalMeshArgs.bMapMorphTargetToTimeZero; ImportedSuccessfulLodIndex = SuccessfulLodIndex; SuccessfulLodIndex++; } else { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_SkeletalMeshLOD", "Failed to import Skeletal mesh LOD.")), FFbxErrors::SkeletalMesh_LOD_FailedToImport); } } bOperationCanceled |= SlowTask.ShouldCancel(); // import morph target if (!bOperationCanceled && BaseSkeletalMesh && ImportOptions->bImportMorph && ImportedSuccessfulLodIndex != INDEX_NONE) { // Disable material importing when importing morph targets uint32 bImportMaterials = ImportOptions->bImportMaterials; ImportOptions->bImportMaterials = 0; uint32 bImportTextures = ImportOptions->bImportTextures; ImportOptions->bImportTextures = 0; FbxImporter->ImportFbxMorphTarget(SkelMeshNodeArray, BaseSkeletalMesh, ImportedSuccessfulLodIndex, OutData, bMapMorphTargetToTimeZero); bOperationCanceled |= FbxImporter->GetImportOperationCancelled(); ImportOptions->bImportMaterials = !!bImportMaterials; ImportOptions->bImportTextures = !!bImportTextures; } bOperationCanceled |= SlowTask.ShouldCancel(); } if (BaseSkeletalMesh && !bOperationCanceled) { NodeIndex++; FFormatNamedArguments Args; Args.Add( TEXT("NodeIndex"), NodeIndex ); Args.Add( TEXT("ArrayLength"), SkelMeshArray.Num() ); SlowTask.EnterProgressFrame(0, GetImportTaskText(FText::Format(NSLOCTEXT("UnrealEd", "Importingf", "Importing ({NodeIndex} of {ArrayLength})"), Args))); UnFbx::FFbxImporter::UpdateSkeletalMeshImportData(BaseSkeletalMesh, ImportUI->SkeletalMeshImportData, INDEX_NONE, nullptr, nullptr); if (CreatedObject == nullptr) { CreatedObject = BaseSkeletalMesh; } else if(CreatedObject != BaseSkeletalMesh) { AdditionalImportedObjects.Add(BaseSkeletalMesh); } } bOperationCanceled |= SlowTask.ShouldCancel(); } for (int32 i = 0; i < SkelMeshArray.Num(); i++) { delete SkelMeshArray[i]; } // if total nodes we found is 0, we didn't find anything. if (TotalNumNodes == 0) { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_NoMeshFoundOnRoot", "Could not find any valid mesh on the root hierarchy. If you have mesh in the sub hierarchy, please enable option of [Import Meshes In Bone Hierarchy] when import.")), FFbxErrors::SkeletalMesh_NoMeshFoundOnRoot); } } else if (bCanImportAnimSequence && ImportUI->MeshTypeToImport == FBXIT_Animation )// animation { if (ImportOptions->SkeletonForAnimation) { // will return the last animation sequence that were added CreatedObject = UEditorEngine::ImportFbxAnimation( ImportOptions->SkeletonForAnimation, InParent, ImportUI->AnimSequenceImportData, *Filename, *Name.ToString(), true ); } } } else { if (RootNodeToImport == NULL) { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_InvalidRoot", "Could not find root node.")), FFbxErrors::SkeletalMesh_InvalidRoot); } else if (ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh) { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_InvalidBone", "Failed to find any bone hierarchy. Try disabling the \"Import As Skeletal\" option to import as a rigid mesh. ")), FFbxErrors::SkeletalMesh_InvalidBone); } else { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_InvalidNode", "Could not find any node.")), FFbxErrors::SkeletalMesh_InvalidNode); } } } if (CreatedObject == NULL && !bOperationCanceled) { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_NoObject", "Import failed.")), FFbxErrors::Generic_ImportingNewObjectFailed); } } if (bOperationCanceled) { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Info, LOCTEXT("FailedToImport_OperationCanceled", "Import was canceled.")), FFbxErrors::Generic_ImportingNewObjectFailed); AdditionalImportedObjects.Empty(); CancelObjectCreation(FbxImporter); CreatedObject = nullptr; } else { FbxImporter->ReleaseScene(); GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, CreatedObject); } bOutOperationCanceled = bOperationCanceled; return CreatedObject; } void UFbxFactory::CancelObjectCreation(UnFbx::FFbxImporter* FbxImporter) const { FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked("AssetRegistry"); const TArray>& CreatedObjects = FbxImporter->GetCreatedObjects(); TArray AssetsToDelete; TArray> PotentialPackageToDeleteReferences; // Flag for deletion the newly created objects in memory. for (int32 CreatedObjectIndex = 0; CreatedObjectIndex < CreatedObjects.Num(); ++CreatedObjectIndex) { const TWeakObjectPtr& CurrentObject = CreatedObjects[CreatedObjectIndex]; if (CurrentObject.IsValid()) { AssetsToDelete.Emplace(AssetRegistryModule.Get().GetAssetByObjectPath(FSoftObjectPath(CurrentObject.Get()))); PotentialPackageToDeleteReferences.Add(CurrentObject->GetOutermost()); CurrentObject->ClearFlags(RF_Standalone | RF_Public); CurrentObject->RemoveFromRoot(); CurrentObject->MarkAsGarbage(); } } CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); // Delete any remaining newly created assets. TArray CreatedObjectsPtr; for (int32 CreatedObjectIndex = 0; CreatedObjectIndex < AssetsToDelete.Num(); ++CreatedObjectIndex) { const FAssetData& CurrentObject = AssetsToDelete[CreatedObjectIndex]; UObject* AssetToDelete = CurrentObject.GetAsset(); if (AssetToDelete != nullptr) { CreatedObjectsPtr.AddUnique(AssetToDelete); } } bool bObjectsDeleted = ObjectTools::ForceDeleteObjects(CreatedObjectsPtr, false) > 0; if (bObjectsDeleted) { CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); } FbxImporter->ReleaseScene(); //We make sure sure to cleanup any remaining empty packages, otherwise we will be prompted to save them even if they don't contain any asset. TArray PotentialPackageToDelete; for (int32 PackageIndex = PotentialPackageToDeleteReferences.Num() - 1; PackageIndex >= 0; --PackageIndex) { if (PotentialPackageToDeleteReferences[PackageIndex].IsValid()) { PotentialPackageToDelete.Add(PotentialPackageToDeleteReferences[PackageIndex].Get()); } } ObjectTools::CleanupAfterSuccessfulDelete(PotentialPackageToDelete); //Make sure the content browser is in sync after we delete, to avoid stale references. FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked("ContentBrowser"); const bool bAllowLockedBrowser = true; ContentBrowserModule.Get().SyncBrowserToAssets(AssetsToDelete, bAllowLockedBrowser); } UObject* UFbxFactory::RecursiveImportNode(UnFbx::FFbxImporter* FbxImporter, void* VoidNode, UObject* InParent, FName InName, EObjectFlags Flags, FScopedSlowTask& SlowTask, TArray& OutNewAssets) { TArray TmpVoidArray; UObject* CreatedObject = NULL; FbxNode* Node = (FbxNode*)VoidNode; bOperationCanceled |= SlowTask.ShouldCancel(); if (bOperationCanceled) { return CreatedObject; } if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup && Node->GetChildCount() > 0 ) { TArray AllNodeInLod; // import base mesh FbxImporter->FindAllLODGroupNode(AllNodeInLod, Node, 0); if (AllNodeInLod.Num() > 0) { TmpVoidArray.Empty(); for (FbxNode* LodNode : AllNodeInLod) { TmpVoidArray.Add(LodNode); } CreatedObject = ImportANode(FbxImporter, TmpVoidArray, InParent, InName, Flags, SlowTask); } if (CreatedObject) { OutNewAssets.AddUnique(CreatedObject); } bool bImportMeshLODs = ImportUI->StaticMeshImportData->bImportMeshLODs; if (CreatedObject && bImportMeshLODs) { // import LOD meshes SlowTask.TotalAmountOfWork += Node->GetChildCount(); for (int32 LODIndex = 1; LODIndex < Node->GetChildCount(); LODIndex++) { if (LODIndex >= MAX_STATIC_MESH_LODS) { SlowTask.CompletedWork += Node->GetChildCount() - LODIndex; FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format( LOCTEXT("ImporterLimits_MaximumStaticMeshLODReach", "Reached the maximum number of LODs for a Static Mesh({0}) - discarding {1} LOD meshes."), FText::AsNumber(MAX_STATIC_MESH_LODS), FText::AsNumber(Node->GetChildCount() - MAX_STATIC_MESH_LODS)) ), FFbxErrors::Generic_Mesh_TooManyLODs); break; } bOperationCanceled |= SlowTask.ShouldCancel(); if (bOperationCanceled) { break; } AllNodeInLod.Empty(); FbxImporter->FindAllLODGroupNode(AllNodeInLod, Node, LODIndex); if (AllNodeInLod.Num() > 0) { if (AllNodeInLod[0]->GetMesh() == nullptr) { UStaticMesh* NewStaticMesh = Cast(CreatedObject); //Add a Lod generated model while (NewStaticMesh->GetNumSourceModels() <= LODIndex) { NewStaticMesh->AddSourceModel(); } ImportANode(FbxImporter, TmpVoidArray, InParent, InName, Flags, SlowTask, CreatedObject, LODIndex); FStaticMeshSourceModel& ThisSourceModel = NewStaticMesh->GetSourceModel(LODIndex); if (LODIndex - 1 > 0 && NewStaticMesh->IsReductionActive(LODIndex - 1)) { const FStaticMeshSourceModel& PrevSourceModel = NewStaticMesh->GetSourceModel(LODIndex - 1); //Do not add the LODGroup bias here, since the bias will be apply during the build if (PrevSourceModel.ReductionSettings.PercentTriangles < 1.0f) { ThisSourceModel.ReductionSettings.PercentTriangles = PrevSourceModel.ReductionSettings.PercentTriangles * 0.5f; } else if (PrevSourceModel.ReductionSettings.MaxDeviation > 0.0f) { ThisSourceModel.ReductionSettings.MaxDeviation = PrevSourceModel.ReductionSettings.MaxDeviation + 1.0f; } } else { ThisSourceModel.ReductionSettings.PercentTriangles = FMath::Pow(0.5f, (float)LODIndex); } } else { TmpVoidArray.Empty(); for (FbxNode* LodNode : AllNodeInLod) { TmpVoidArray.Add(LodNode); } ImportANode(FbxImporter, TmpVoidArray, InParent, InName, Flags, SlowTask, CreatedObject, LODIndex); UStaticMesh* NewStaticMesh = Cast(CreatedObject); if(NewStaticMesh->IsSourceModelValid(LODIndex)) { NewStaticMesh->GetSourceModel(LODIndex).bImportWithBaseMesh = true; } } } } } if (CreatedObject) { UStaticMesh *NewStaticMesh = Cast(CreatedObject); if (NewStaticMesh != nullptr) { //Reorder the material TArray Nodes; FbxImporter->FindAllLODGroupNode(Nodes, Node, 0); if (Nodes.Num() > 0) { FbxImporter->PostImportStaticMesh(NewStaticMesh, Nodes); FbxImporter->UpdateStaticMeshImportData(NewStaticMesh, nullptr); } } } } else { if (Node->GetMesh()) { TmpVoidArray.Empty(); TmpVoidArray.Add(Node); CreatedObject = ImportANode(FbxImporter, TmpVoidArray, InParent, InName, Flags, SlowTask); if (CreatedObject) { UStaticMesh *NewStaticMesh = Cast(CreatedObject); if (NewStaticMesh != nullptr) { //Reorder the material TArray Nodes; Nodes.Add(Node); FbxImporter->PostImportStaticMesh(NewStaticMesh, Nodes); FbxImporter->UpdateStaticMeshImportData(NewStaticMesh, nullptr); } OutNewAssets.AddUnique(CreatedObject); } } else { SlowTask.CompletedWork++; } SlowTask.TotalAmountOfWork += Node->GetChildCount(); for (int32 ChildIndex=0; ChildIndexGetChildCount(); ++ChildIndex) { UObject* SubObject = RecursiveImportNode(FbxImporter, Node->GetChild(ChildIndex), InParent, InName, Flags, SlowTask, OutNewAssets); if ( SubObject ) { OutNewAssets.AddUnique(SubObject); } if (CreatedObject ==NULL) { CreatedObject = SubObject; } } } return CreatedObject; } void UFbxFactory::CleanUp() { UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(true/*bDoNotCreate*/); bDetectImportTypeOnImport = true; bShowOption = true; // load options if (FFbxImporter) { struct UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions(); if ( ImportOptions ) { ImportOptions->SkeletonForAnimation = NULL; ImportOptions->PhysicsAsset = NULL; ImportOptions->bIsImportCancelable = false; } } Super::CleanUp(); } bool UFbxFactory::FactoryCanImport(const FString& Filename) { const FString Extension = FPaths::GetExtension(Filename); if( Extension == TEXT("fbx") || Extension == TEXT("obj") ) { return true; } return false; } bool UFbxFactory::CanImportBeCanceled() const { return true; } IImportSettingsParser* UFbxFactory::GetImportSettingsParser() { return ImportUI; } TArray UFbxFactory::GetFormats() const { return GetFbxFormats(this); } TArray UFbxFactory::GetFbxFormats(const UFactory* Factory) { TArray FormatArray; const IConsoleVariable* CVarFbx = IConsoleManager::Get().FindConsoleVariable(TEXT("Interchange.FeatureFlags.Import.FBX")); const IConsoleVariable* CVarFbxLevel = IConsoleManager::Get().FindConsoleVariable(TEXT("Interchange.FeatureFlags.Import.FBX.ToLevel")); const bool bUseLegacyFbx = (!CVarFbx || !CVarFbx->GetBool()) || (!CVarFbxLevel || !CVarFbxLevel->GetBool()); const IConsoleVariable* CVarObj = IConsoleManager::Get().FindConsoleVariable(TEXT("Interchange.FeatureFlags.Import.OBJ")); const bool bUseLegacyObj = (!CVarObj || !CVarObj->GetBool()); for (const FString& Format : Factory->Formats) { if (Format.StartsWith(TEXT("fbx"))) { //Skip if interchange fbx is enabled if (bUseLegacyFbx) { FormatArray.Add(Format); } } else if (Format.StartsWith(TEXT("obj"))) { //Skip if interchange obj is enabled if (bUseLegacyObj) { FormatArray.Add(Format); } } else { FormatArray.Add(Format); } } return FormatArray; } UFbxImportUI::UFbxImportUI(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bIsReimport = false; ReimportMesh = nullptr; bAllowContentTypeImport = false; bAutomatedImportShouldDetectType = true; bResetToFbxOnMaterialConflict = false; //Make sure we are transactional to allow undo redo this->SetFlags(RF_Transactional); StaticMeshImportData = CreateDefaultSubobject(TEXT("StaticMeshImportData")); StaticMeshImportData->SetFlags(RF_Transactional); UFbxImportUI::LoadOptions(StaticMeshImportData); SkeletalMeshImportData = CreateDefaultSubobject(TEXT("SkeletalMeshImportData")); SkeletalMeshImportData->SetFlags(RF_Transactional); UFbxImportUI::LoadOptions(SkeletalMeshImportData); AnimSequenceImportData = CreateDefaultSubobject(TEXT("AnimSequenceImportData")); AnimSequenceImportData->SetFlags(RF_Transactional); UFbxImportUI::LoadOptions(AnimSequenceImportData); TextureImportData = CreateDefaultSubobject(TEXT("TextureImportData")); TextureImportData->SetFlags(RF_Transactional); UFbxImportUI::LoadOptions(TextureImportData); } bool UFbxImportUI::CanEditChange( const FProperty* InProperty ) const { bool bIsMutable = Super::CanEditChange( InProperty ); if( bIsMutable && InProperty != NULL ) { FName PropName = InProperty->GetFName(); if(PropName == TEXT("FrameImportRange")) { bIsMutable = AnimSequenceImportData->AnimationLength == FBXALIT_SetRange && bImportAnimations; } else if(PropName == TEXT("bImportCustomAttribute") || PropName == TEXT("AnimationLength") || PropName == TEXT("CustomSampleRate") || PropName == TEXT("bUseDefaultSampleRate")) { bIsMutable = bImportAnimations; } if(bIsObjImport && InProperty->GetBoolMetaData(TEXT("OBJRestrict"))) { bIsMutable = false; } } return bIsMutable; } void UFbxImportUI::ParseFromJson(TSharedRef ImportSettingsJson) { // Skip instanced object references. int64 SkipFlags = CPF_InstancedReference; FJsonObjectConverter::JsonObjectToUStruct(ImportSettingsJson, GetClass(), this, 0, SkipFlags); bAutomatedImportShouldDetectType = true; if(ImportSettingsJson->TryGetField(TEXT("MeshTypeToImport")).IsValid()) { // Import type was specified by the user if MeshTypeToImport exists bAutomatedImportShouldDetectType = false; } const TSharedPtr* StaticMeshImportJson = nullptr; ImportSettingsJson->TryGetObjectField(TEXT("StaticMeshImportData"), StaticMeshImportJson); if(StaticMeshImportJson) { FJsonObjectConverter::JsonObjectToUStruct(StaticMeshImportJson->ToSharedRef(), StaticMeshImportData->GetClass(), StaticMeshImportData, 0, 0); } const TSharedPtr* SkeletalMeshImportJson = nullptr; ImportSettingsJson->TryGetObjectField(TEXT("SkeletalMeshImportData"), SkeletalMeshImportJson); if (SkeletalMeshImportJson) { FJsonObjectConverter::JsonObjectToUStruct(SkeletalMeshImportJson->ToSharedRef(), SkeletalMeshImportData->GetClass(), SkeletalMeshImportData, 0, 0); } const TSharedPtr* AnimImportJson = nullptr; ImportSettingsJson->TryGetObjectField(TEXT("AnimSequenceImportData"), AnimImportJson); if (AnimImportJson) { FJsonObjectConverter::JsonObjectToUStruct(AnimImportJson->ToSharedRef(), AnimSequenceImportData->GetClass(), AnimSequenceImportData, 0, 0); } const TSharedPtr* TextureImportJson = nullptr; ImportSettingsJson->TryGetObjectField(TEXT("TextureImportData"), TextureImportJson); if (TextureImportJson) { FJsonObjectConverter::JsonObjectToUStruct(TextureImportJson->ToSharedRef(), TextureImportData->GetClass(), TextureImportData, 0, 0); } } void UFbxImportUI::ResetToDefault() { ReloadConfig(); AnimSequenceImportData->ReloadConfig(); StaticMeshImportData->ReloadConfig(); SkeletalMeshImportData->ReloadConfig(); TextureImportData->ReloadConfig(); //Make sure the UI do not display the Base Material TextureImportData->bUseBaseMaterial = false; } namespace ImportCompareHelper { void SetHasConflict(FMaterialCompareData& MaterialCompareData) { MaterialCompareData.bHasConflict = false; for (const FMaterialData& ResultMaterial : MaterialCompareData.ResultAsset) { bool bFoundMatch = false; for (const FMaterialData& CurrentMaterial : MaterialCompareData.CurrentAsset) { if (ResultMaterial.ImportedMaterialSlotName == CurrentMaterial.ImportedMaterialSlotName) { bFoundMatch = true; break; } } if (!bFoundMatch) { MaterialCompareData.bHasConflict = true; break; } } } bool HasRemoveBoneRecursive(const FSkeletonTreeNode& ResultAssetRoot, const FSkeletonTreeNode& CurrentAssetRoot) { //Find the removed node for (const FSkeletonTreeNode& CurrentNode : CurrentAssetRoot.Childrens) { bool bFoundMatch = false; for (const FSkeletonTreeNode& ResultNode : ResultAssetRoot.Childrens) { if (ResultNode.JointName == CurrentNode.JointName) { bFoundMatch = !HasRemoveBoneRecursive(ResultNode, CurrentNode); break; } } if (!bFoundMatch) { return true; } } return false; } bool HasAddedBoneRecursive(const FSkeletonTreeNode& ResultAssetRoot, const FSkeletonTreeNode& CurrentAssetRoot) { //Find the added node for (const FSkeletonTreeNode& ResultNode : ResultAssetRoot.Childrens) { bool bFoundMatch = false; for (const FSkeletonTreeNode& CurrentNode : CurrentAssetRoot.Childrens) { if (ResultNode.JointName == CurrentNode.JointName) { bFoundMatch = !HasAddedBoneRecursive(ResultNode, CurrentNode); break; } } if (!bFoundMatch) { return true; } } return false; } void SetHasConflict(FSkeletonCompareData& SkeletonCompareData) { //Clear the skeleton Result SkeletonCompareData.CompareResult = ECompareResult::SCR_None; if (SkeletonCompareData.ResultAssetRoot.JointName != SkeletonCompareData.CurrentAssetRoot.JointName) { SkeletonCompareData.CompareResult = ECompareResult::SCR_SkeletonBadRoot; return; } if (HasRemoveBoneRecursive(SkeletonCompareData.ResultAssetRoot, SkeletonCompareData.CurrentAssetRoot)) { SkeletonCompareData.CompareResult |= ECompareResult::SCR_SkeletonMissingBone; } if (HasAddedBoneRecursive(SkeletonCompareData.ResultAssetRoot, SkeletonCompareData.CurrentAssetRoot)) { SkeletonCompareData.CompareResult |= ECompareResult::SCR_SkeletonAddedBone; } } void FillFbxMaterials(UnFbx::FFbxImporter* FFbxImporter, const TArray& MeshNodes, FMaterialCompareData& MaterialCompareData) { TArray NodeMaterialNames; for (int32 NodeIndex = 0; NodeIndex < MeshNodes.Num(); ++NodeIndex) { FbxNode* Node = MeshNodes[NodeIndex]; if (Node->GetMesh() == nullptr) { continue; } int32 MaterialCount = Node->GetMaterialCount(); TArray MaterialUseByMesh; FbxLayer* BaseLayer = Node->GetMesh()->GetLayer(0); FbxLayerElementMaterial* MateriallayerElement = BaseLayer->GetMaterials(); FbxLayerElement::EMappingMode MaterialMappingMode = MateriallayerElement ? MateriallayerElement->GetMappingMode() : FbxLayerElement::eByPolygon; if (MaterialMappingMode == FbxLayerElement::eAllSame || MaterialCount == 0 || MateriallayerElement == nullptr) { MaterialUseByMesh.Add(0); } else { int32 PolygonCount = Node->GetMesh()->GetPolygonCount(); for (int32 PolygonIndex = 0; PolygonIndex < PolygonCount; PolygonIndex++) { MaterialUseByMesh.AddUnique(MateriallayerElement->GetIndexArray().GetAt(PolygonIndex)); } } for (int32 MaterialIndex = 0; MaterialIndex < MaterialCount; ++MaterialIndex) { //Skip unused mesh material if (!MaterialUseByMesh.Contains(MaterialIndex)) { continue; } FbxSurfaceMaterial* SurfaceMaterial = Node->GetMaterial(MaterialIndex); FName SurfaceMaterialName = FName(*(FFbxImporter->MakeName(SurfaceMaterial->GetName()))); if (!NodeMaterialNames.Contains(SurfaceMaterialName)) { FMaterialData& MaterialData = MaterialCompareData.ResultAsset.AddDefaulted_GetRef(); MaterialData.ImportedMaterialSlotName = SurfaceMaterialName; MaterialData.MaterialSlotName = SurfaceMaterialName; MaterialData.MaterialIndex = NodeMaterialNames.Add(SurfaceMaterialName); } } } } void FillRecursivelySkeleton(const FReferenceSkeleton& ReferenceSkeleton, int32 CurrentIndex, FSkeletonTreeNode& SkeletonTreeNode) { SkeletonTreeNode.JointName = ReferenceSkeleton.GetBoneName(CurrentIndex); const int32 NumBones = ReferenceSkeleton.GetNum(); for (int32 ChildIndex = CurrentIndex + 1; ChildIndex < NumBones; ChildIndex++) { if (CurrentIndex == ReferenceSkeleton.GetParentIndex(ChildIndex)) { //Skip virtual bones if (ReferenceSkeleton.GetRequiredVirtualBones().Contains(ChildIndex)) { return; } FSkeletonTreeNode& ChildNode = SkeletonTreeNode.Childrens.AddDefaulted_GetRef(); ChildNode.JointName = ReferenceSkeleton.GetBoneName(ChildIndex); FillRecursivelySkeleton(ReferenceSkeleton, ChildIndex, ChildNode); } } } void FillRecursivelySkeletonCompareData(const FbxNode *ParentNode, FSkeletonTreeNode& SkeletonTreeNode, const UnFbx::FFbxImporter* FFbxImporter) { SkeletonTreeNode.JointName = FName(*(FFbxImporter->MakeName(ParentNode->GetName()))); for (int32 ChildIndex = 0; ChildIndex < ParentNode->GetChildCount(); ChildIndex++) { FSkeletonTreeNode& ChildNode = SkeletonTreeNode.Childrens.AddDefaulted_GetRef(); FillRecursivelySkeletonCompareData(ParentNode->GetChild(ChildIndex), ChildNode, FFbxImporter); } } void FillFbxSkeleton(UnFbx::FFbxImporter* FFbxImporter, const TArray& SkeletalMeshNodes, FSkeletonCompareData& SkeletonCompareData) { TArray JointLinks; FbxNode* Link = NULL; if (SkeletalMeshNodes.Num() > 0) { bool bHasLOD = false; FbxNode* SkeletalMeshRootNode = SkeletalMeshNodes[0]; if (SkeletalMeshRootNode->GetNodeAttribute() && SkeletalMeshRootNode->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { // Use the first LOD group node to build the skeleton SkeletalMeshRootNode = FFbxImporter->FindLODGroupNode(SkeletalMeshNodes[0], 0); bHasLOD = true; } if (SkeletalMeshRootNode->GetMesh()) { if(SkeletalMeshRootNode->GetMesh()->GetDeformerCount(FbxDeformer::eSkin) == 0) { Link = SkeletalMeshRootNode; FFbxImporter->RecursiveBuildSkeleton(FFbxImporter->GetRootSkeleton(Link), JointLinks); } else { TArray ClusterArray; for (int32 i = 0; i < SkeletalMeshNodes.Num(); i++) { FbxMesh* FbxMesh = (i == 0 && bHasLOD) ? SkeletalMeshRootNode->GetMesh() : SkeletalMeshNodes[i]->GetMesh(); if (!FbxMesh) { continue; } const int32 SkinDeformerCount = FbxMesh->GetDeformerCount(FbxDeformer::eSkin); for (int32 DeformerIndex = 0; DeformerIndex < SkinDeformerCount; DeformerIndex++) { FbxSkin* Skin = (FbxSkin*)FbxMesh->GetDeformer(DeformerIndex, FbxDeformer::eSkin); for (int32 ClusterIndex = 0; ClusterIndex < Skin->GetClusterCount(); ClusterIndex++) { ClusterArray.Add(Skin->GetCluster(ClusterIndex)); } } } // recurse through skeleton and build ordered table FFbxImporter->BuildSkeletonSystem(ClusterArray, JointLinks); } } //Fill the Result skeleton data FillRecursivelySkeletonCompareData(JointLinks[0], SkeletonCompareData.ResultAssetRoot, FFbxImporter); } } void RecursiveAddMeshNode(UnFbx::FFbxImporter* FFbxImporter, FbxNode* ParentNode, TArray& FlattenMeshNodes) { if (ParentNode->GetMesh()) { FlattenMeshNodes.Add(ParentNode); } else if (ParentNode->GetNodeAttribute() && ParentNode->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { //In case we have some LODs, just grab the LOD 0 meshes ParentNode = FFbxImporter->FindLODGroupNode(ParentNode, 0); if (ParentNode == nullptr) { return; } FlattenMeshNodes.Add(ParentNode); } for (int32 ChildIndex = 0; ChildIndex < ParentNode->GetChildCount(); ++ChildIndex) { RecursiveAddMeshNode(FFbxImporter, ParentNode->GetChild(ChildIndex), FlattenMeshNodes); } } void FillStaticMeshCompareData(UnFbx::FFbxImporter* FFbxImporter, UStaticMesh* StaticMesh, UFbxImportUI* ImportUI) { //Fill the currrent asset data ImportUI->MaterialCompareData.CurrentAsset.Reserve(StaticMesh->GetStaticMaterials().Num()); for (int32 MaterialIndex = 0; MaterialIndex < StaticMesh->GetStaticMaterials().Num(); ++MaterialIndex) { const FStaticMaterial& Material = StaticMesh->GetStaticMaterials()[MaterialIndex]; FMaterialData MaterialData; MaterialData.MaterialIndex = MaterialIndex; MaterialData.ImportedMaterialSlotName = Material.ImportedMaterialSlotName; MaterialData.MaterialSlotName = Material.MaterialSlotName; ImportUI->MaterialCompareData.CurrentAsset.Add(MaterialData); } //Find the array of nodes to re-import TArray FbxMeshArray; bool bImportStaticMeshLODs = ImportUI->StaticMeshImportData->bImportMeshLODs; bool bCombineMeshes = ImportUI->StaticMeshImportData->bCombineMeshes; bool bCombineMeshesLOD = false; TArray> FbxMeshesLod; FbxNode* Node = nullptr; if (bCombineMeshes && !bImportStaticMeshLODs) { FFbxImporter->FillFbxMeshArray(FFbxImporter->Scene->GetRootNode(), FbxMeshArray, FFbxImporter); } else { // count meshes in lod groups if we dont care about importing LODs bool bCountLODGroupMeshes = !bImportStaticMeshLODs && bCombineMeshes; int32 NumLODGroups = 0; FFbxImporter->GetFbxMeshCount(FFbxImporter->Scene->GetRootNode(), bCountLODGroupMeshes, NumLODGroups); // if there were LODs in the file, do not combine meshes even if requested if (bImportStaticMeshLODs && bCombineMeshes && NumLODGroups > 0) { TArray FbxLodGroups; FFbxImporter->FillFbxMeshAndLODGroupArray(FFbxImporter->Scene->GetRootNode(), FbxLodGroups, FbxMeshArray); FbxMeshesLod.Add(FbxMeshArray); for (FbxNode* LODGroup : FbxLodGroups) { if (LODGroup->GetNodeAttribute() && LODGroup->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup && LODGroup->GetChildCount() > 0) { for (int32 GroupLodIndex = 0; GroupLodIndex < LODGroup->GetChildCount() && GroupLodIndex < MAX_STATIC_MESH_LODS; ++GroupLodIndex) { TArray AllNodeInLod; FFbxImporter->FindAllLODGroupNode(AllNodeInLod, LODGroup, GroupLodIndex); if (AllNodeInLod.Num() > 0) { if (FbxMeshesLod.Num() <= GroupLodIndex) { FbxMeshesLod.Add(AllNodeInLod); } else { TArray &LODGroupArray = FbxMeshesLod[GroupLodIndex]; for (FbxNode* NodeToAdd : AllNodeInLod) { LODGroupArray.Add(NodeToAdd); } } } } } } bCombineMeshesLOD = true; bCombineMeshes = false; //Set the first LOD FbxMeshArray = FbxMeshesLod[0]; } else { FFbxImporter->FillFbxMeshArray(FFbxImporter->Scene->GetRootNode(), FbxMeshArray, FFbxImporter); } } // if there is only one mesh, use it without name checking // (because the "Used As Full Name" option enables users name the Unreal mesh by themselves if (!bCombineMeshesLOD && FbxMeshArray.Num() == 1) { Node = FbxMeshArray[0]; } else if (!bCombineMeshes && !bCombineMeshesLOD) { Node = FFbxImporter->GetMeshNodesFromName(StaticMesh->GetName(), FbxMeshArray); } // If there is no match it may be because an LOD group was imported where // the mesh name does not match the file name. This is actually the common case. if (!bCombineMeshesLOD && !Node && FbxMeshArray.IsValidIndex(0)) { FbxNode* BaseLODNode = FbxMeshArray[0]; FbxNode* NodeParent = BaseLODNode ? FFbxImporter->RecursiveFindParentLodGroup(BaseLODNode->GetParent()) : nullptr; if (NodeParent && NodeParent->GetNodeAttribute() && NodeParent->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { // Reimport the entire LOD chain. Node = BaseLODNode; } } TArray StaticMeshNodes; if (bCombineMeshesLOD) { //FindLOD 0 Material if (FbxMeshesLod.Num() > 0) { StaticMeshNodes = FbxMeshesLod[0]; } //Import all LODs for (int32 LODIndex = 1; LODIndex < FbxMeshesLod.Num(); ++LODIndex) { if (FbxMeshesLod[LODIndex][0]->GetMesh() != nullptr) { StaticMeshNodes.Append(FbxMeshesLod[LODIndex]); } } } else if (Node) { FbxNode* NodeParent = FFbxImporter->RecursiveFindParentLodGroup(Node->GetParent()); // if the Fbx mesh is a part of LODGroup, update LOD if (NodeParent && NodeParent->GetNodeAttribute() && NodeParent->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { TArray AllNodeInLod; FFbxImporter->FindAllLODGroupNode(AllNodeInLod, NodeParent, 0); if (AllNodeInLod.Num() > 0) { StaticMeshNodes.Append(AllNodeInLod); } //If we have a valid LOD group name we don't want to re-import LODs since they will be automatically generate by the LODGroup reduce settings if (bImportStaticMeshLODs && StaticMesh->LODGroup == NAME_None) { // import LOD meshes for (int32 LODIndex = 1; LODIndex < NodeParent->GetChildCount(); LODIndex++) { AllNodeInLod.Empty(); FFbxImporter->FindAllLODGroupNode(AllNodeInLod, NodeParent, LODIndex); if (AllNodeInLod.Num() > 0 && AllNodeInLod[0]->GetMesh() != nullptr) { StaticMeshNodes.Append(AllNodeInLod); } } } } else { StaticMeshNodes.Add(Node); } } else { StaticMeshNodes.Append(FbxMeshArray); } FillFbxMaterials(FFbxImporter, StaticMeshNodes, ImportUI->MaterialCompareData); //Compare the result and set the conflict status SetHasConflict(ImportUI->MaterialCompareData); } void FillSkeletalMeshCompareData(UnFbx::FFbxImporter* FFbxImporter, USkeletalMesh* SkeletalMesh, UFbxImportUI* ImportUI) { bool bImportGeoOnly = ImportUI->SkeletalMeshImportData->ImportContentType == EFBXImportContentType::FBXICT_Geometry; bool bImportSkinningOnly = ImportUI->SkeletalMeshImportData->ImportContentType == EFBXImportContentType::FBXICT_SkinningWeights; //Fill the fbx data, read the scene and found the skeletalmesh nodes FbxNode* SceneRoot = FFbxImporter->Scene->GetRootNode(); TArray*> SkeletalMeshArray; FFbxImporter->FillFbxSkelMeshArrayInScene(SceneRoot, SkeletalMeshArray, false, (bImportGeoOnly || bImportSkinningOnly), false); if (SkeletalMeshArray.Num() == 0) { return; } TArray* FbxNodes = nullptr; // if there is only one mesh, use it without name checking // (because the "Used As Full Name" option enables users name the Unreal mesh by themselves if (SkeletalMeshArray.Num() == 1) { FbxNodes = SkeletalMeshArray[0]; } else if(SkeletalMeshArray.Num() > 1) { FbxNodes = nullptr; //Search a set corresponding for (TArray*SkeletalMeshNodes : SkeletalMeshArray) { FbxNode* Node = FFbxImporter->GetMeshNodesFromName(SkeletalMesh->GetName(), *SkeletalMeshNodes); if (Node != nullptr) { FbxNodes = SkeletalMeshNodes; break; } } if (FbxNodes == nullptr) { //Re import the first skeletalmesh found in the fbx file FbxNodes = SkeletalMeshArray[0]; } } if (FbxNodes == nullptr) { return; } const TArray& SkeletalMeshNodes = *(FbxNodes); if (SkeletalMeshNodes.Num() == 0) { return; } //Materials if (!bImportSkinningOnly) { //Fill the currrent asset data const TArray& SkeletalMeshMaterials = SkeletalMesh->GetMaterials(); ImportUI->MaterialCompareData.CurrentAsset.Reserve(SkeletalMeshMaterials.Num()); for (int32 MaterialIndex = 0; MaterialIndex < SkeletalMeshMaterials.Num(); ++MaterialIndex) { const FSkeletalMaterial& Material = SkeletalMeshMaterials[MaterialIndex]; FMaterialData MaterialData; MaterialData.MaterialIndex = MaterialIndex; MaterialData.ImportedMaterialSlotName = Material.ImportedMaterialSlotName; MaterialData.MaterialSlotName = Material.MaterialSlotName; ImportUI->MaterialCompareData.CurrentAsset.Add(MaterialData); } TArray FlattenSkeletalMeshNodes; for (FbxNode* SkeletalMeshRootNode : SkeletalMeshNodes) { RecursiveAddMeshNode(FFbxImporter, SkeletalMeshRootNode, FlattenSkeletalMeshNodes); } //Fill the result fbx data FillFbxMaterials(FFbxImporter, FlattenSkeletalMeshNodes, ImportUI->MaterialCompareData); //Compare the result and set the conflict status SetHasConflict(ImportUI->MaterialCompareData); } //Skeleton joints if (!bImportGeoOnly) { //Fill the currrent asset data if (ImportUI->Skeleton && SkeletalMesh->GetSkeleton() != ImportUI->Skeleton) { //In this case we can't use const FReferenceSkeleton& ReferenceSkeleton = ImportUI->Skeleton->GetReferenceSkeleton(); FillRecursivelySkeleton(ReferenceSkeleton, 0, ImportUI->SkeletonCompareData.CurrentAssetRoot); } else { FillRecursivelySkeleton(SkeletalMesh->GetRefSkeleton(), 0, ImportUI->SkeletonCompareData.CurrentAssetRoot); } //Fill the result fbx data FillFbxSkeleton(FFbxImporter, SkeletalMeshNodes, ImportUI->SkeletonCompareData); //Compare the result and set the conflict status SetHasConflict(ImportUI->SkeletonCompareData); } } } //END namespace ImportCompareHelper void UFbxImportUI::UpdateCompareData(UnFbx::FFbxImporter* FbxImporter) { if (ReimportMesh == nullptr) { return; } UStaticMesh* StaticMesh = Cast(ReimportMesh); USkeletalMesh* SkeletalMesh = Cast(ReimportMesh); MaterialCompareData.Empty(); SkeletonCompareData.Empty(); FString Filename; if (StaticMesh != nullptr) { Filename = StaticMeshImportData->GetFirstFilename(); } else { FString FilenameLabel; SkeletalMeshImportData->GetImportContentFilename(Filename, FilenameLabel); } if (!FbxImporter->ImportFromFile(*Filename, FPaths::GetExtension(Filename), false)) { return; } if (StaticMesh) { ImportCompareHelper::FillStaticMeshCompareData(FbxImporter, StaticMesh, this); } else if (SkeletalMesh) { ImportCompareHelper::FillSkeletalMeshCompareData(FbxImporter, SkeletalMesh, this); } FbxImporter->PartialCleanUp(); } /** Load UI settings from ini file */ void UFbxImportUI::LoadOptions(UObject* ObjectToLoadOptions) { check(ObjectToLoadOptions); int32 PortFlags = 0; UClass* Class = ObjectToLoadOptions->GetClass(); for (FProperty* Property = Class->PropertyLink; Property; Property = Property->PropertyLinkNext) { if (!Property->HasAnyPropertyFlags(CPF_Config)) { continue; } FString Section = TEXT("FBX_Import_UI_Option_") + Class->GetName(); FString Key = Property->GetName(); const bool bIsPropertyInherited = Property->GetOwnerClass() != Class; UObject* SuperClassDefaultObject = Class->GetSuperClass()->GetDefaultObject(); const FString& PropFileName = GEditorPerProjectIni; FArrayProperty* Array = CastField(Property); if (Array) { const FConfigSection* Sec = GConfig->GetSection(*Section, 0, *GEditorPerProjectIni); if (Sec != nullptr) { TArray List; const FName KeyName(*Key, FNAME_Find); Sec->MultiFind(KeyName, List); FScriptArrayHelper_InContainer ArrayHelper(Array, ObjectToLoadOptions); // Only override default properties if there is something to override them with. if (List.Num() > 0) { ArrayHelper.EmptyAndAddValues(List.Num()); for (int32 i = List.Num() - 1, c = 0; i >= 0; i--, c++) { Array->Inner->ImportText_Direct(*List[i].GetValue(), ArrayHelper.GetRawPtr(c), ObjectToLoadOptions, PortFlags); } } else { int32 Index = 0; const FConfigValue* ElementValue = nullptr; do { // Add array index number to end of key FString IndexedKey = FString::Printf(TEXT("%s[%i]"), *Key, Index); // Try to find value of key const FName IndexedName(*IndexedKey, FNAME_Find); if (IndexedName == NAME_None) { break; } ElementValue = Sec->Find(IndexedName); // If found, import the element if (ElementValue != nullptr) { // expand the array if necessary so that Index is a valid element ArrayHelper.ExpandForIndex(Index); Array->Inner->ImportText_Direct(*ElementValue->GetValue(), ArrayHelper.GetRawPtr(Index), ObjectToLoadOptions, PortFlags); } Index++; } while (ElementValue || Index < ArrayHelper.Num()); } } } else { for (int32 i = 0; i < Property->ArrayDim; i++) { if (Property->ArrayDim != 1) { Key = FString::Printf(TEXT("%s[%i]"), *Property->GetName(), i); } FString Value; bool bFoundValue = GConfig->GetString(*Section, *Key, Value, *GEditorPerProjectIni); if (bFoundValue) { if (Property->ImportText_Direct(*Value, Property->ContainerPtrToValuePtr(ObjectToLoadOptions, i), ObjectToLoadOptions, PortFlags) == NULL) { // this should be an error as the properties from the .ini / .int file are not correctly being read in and probably are affecting things in subtle ways UE_LOG(LogFbx, Error, TEXT("FBX Options LoadOptions (%s): import failed for %s in: %s"), *ObjectToLoadOptions->GetPathName(), *Property->GetName(), *Value); } } } } } } /** Save UI settings to ini file */ void UFbxImportUI::SaveOptions(UObject* ObjectToSaveOptions) { check(ObjectToSaveOptions); int32 PortFlags = 0; UClass* Class = ObjectToSaveOptions->GetClass(); for (FProperty* Property = Class->PropertyLink; Property; Property = Property->PropertyLinkNext) { if (!Property->HasAnyPropertyFlags(CPF_Config)) { continue; } FString Section = TEXT("FBX_Import_UI_Option_") + Class->GetName(); FString Key = Property->GetName(); const bool bIsPropertyInherited = Property->GetOwnerClass() != Class; UObject* SuperClassDefaultObject = Class->GetSuperClass()->GetDefaultObject(); FArrayProperty* Array = CastField(Property); if (Array) { GConfig->RemoveKeyFromSection(*Section, *Key, GEditorPerProjectIni); FScriptArrayHelper_InContainer ArrayHelper(Array, ObjectToSaveOptions); for (int32 i = 0; i < ArrayHelper.Num(); i++) { FString Buffer; Array->Inner->ExportTextItem_Direct(Buffer, ArrayHelper.GetRawPtr(i), ArrayHelper.GetRawPtr(i), ObjectToSaveOptions, PortFlags); GConfig->AddToSection(*Section, *Key, Buffer, GEditorPerProjectIni); } } else { TCHAR TempKey[MAX_SPRINTF] = TEXT(""); for (int32 Index = 0; Index < Property->ArrayDim; Index++) { if (Property->ArrayDim != 1) { FCString::Sprintf(TempKey, TEXT("%s[%i]"), *Property->GetName(), Index); Key = TempKey; } FString Value; Property->ExportText_InContainer(Index, Value, ObjectToSaveOptions, ObjectToSaveOptions, ObjectToSaveOptions, PortFlags); GConfig->SetString(*Section, *Key, *Value, *GEditorPerProjectIni); } } } GConfig->Flush(0); } #undef LOCTEXT_NAMESPACE