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

2038 lines
72 KiB
C++

// 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<UFbxImportUI>(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<void*> 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<FbxNode*> 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<UStaticMesh>(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<UImportSubsystem>()->BroadcastAssetPostImport(this, NULL);
return NULL;
}
GEditor->GetEditorSubsystem<UImportSubsystem>()->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<UStaticMesh>(ExistingObject))
{
bIsObjectSupported = true;
// Using RF_NoFlags because we want to include class default object, which are used by the reimport manager.
if (TObjectIterator<UReimportFbxStaticMeshFactory> ReimportFactoryIterator(RF_NoFlags); ReimportFactoryIterator)
{
HandlerOriginalImportTask = ReimportFactoryIterator->AssetImportTask;
ReimportFactoryIterator->AssetImportTask = AssetImportTask;
ReimportHandler = *ReimportFactoryIterator;
ReimportHandlerFactory = *ReimportFactoryIterator;
}
}
else if (Cast<USkeletalMesh>(ExistingObject))
{
bIsObjectSupported = true;
// Using RF_NoFlags because we want to include class default object, which are used by the reimport manager.
if (TObjectIterator<UReimportFbxSkeletalMeshFactory> ReimportFactoryIterator(RF_NoFlags); ReimportFactoryIterator)
{
HandlerOriginalImportTask = ReimportFactoryIterator->AssetImportTask;
ReimportFactoryIterator->AssetImportTask = AssetImportTask;
ReimportHandler = *ReimportFactoryIterator;
ReimportHandlerFactory = *ReimportFactoryIterator;
}
}
else if (Cast<UAnimSequence>(ExistingObject))
{
bIsObjectSupported = true;
// Using RF_NoFlags because we want to include class default object, which are used by the reimport manager.
if (TObjectIterator<UReimportFbxAnimSequenceFactory> ReimportFactoryIterator(RF_NoFlags); ReimportFactoryIterator)
{
HandlerOriginalImportTask = ReimportFactoryIterator->AssetImportTask;
ReimportFactoryIterator->AssetImportTask = AssetImportTask;
ReimportHandler = *ReimportFactoryIterator;
ReimportHandlerFactory = *ReimportFactoryIterator;
}
}
if (bIsObjectSupported)
{
TArray<UObject*> ToReimportObjects;
ToReimportObjects.Add(ExistingObject);
TArray<FString> 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<UImportSubsystem>()->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<UFbxImportUI>(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<FbxNode*>* > 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<FbxNode*> 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<FbxNode*> FbxMeshArray;
TArray<FbxNode*> FbxLodGroups;
TArray<TArray<FbxNode*>> 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<FbxNode*> AllNodeInLod;
FbxImporter->FindAllLODGroupNode(AllNodeInLod, LODGroup, GroupLodIndex);
if (AllNodeInLod.Num() > 0)
{
if (FbxMeshesLod.Num() <= GroupLodIndex)
{
FbxMeshesLod.Add(AllNodeInLod);
}
else
{
TArray<FbxNode*> &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<FbxNode*> &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<FbxNode*> &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<UObject*> AllNewAssets;
UObject* Object = RecursiveImportNode(FbxImporter, RootNodeToImport, InParent, Name, Flags, ImportNodeSlowTask, AllNewAssets);
bOperationCanceled |= SlowTask.ShouldCancel();
NewStaticMesh = Cast<UStaticMesh>( 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<UPackage>(InParent) : nullptr);
USkeletalMesh* BaseSkeletalMesh = nullptr;
TArray<FbxNode*> 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<FbxNode*> SkelMeshNodeArray;
for (int32 j = 0; j < NodeArray.Num(); j++)
{
FbxNode* Node = NodeArray[j];
if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup)
{
TArray<FbxNode*> 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<UImportSubsystem>()->BroadcastAssetPostImport(this, CreatedObject);
}
bOutOperationCanceled = bOperationCanceled;
return CreatedObject;
}
void UFbxFactory::CancelObjectCreation(UnFbx::FFbxImporter* FbxImporter) const
{
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
const TArray<TWeakObjectPtr<UObject>>& CreatedObjects = FbxImporter->GetCreatedObjects();
TArray<FAssetData> AssetsToDelete;
TArray<TWeakObjectPtr<UPackage>> PotentialPackageToDeleteReferences;
// Flag for deletion the newly created objects in memory.
for (int32 CreatedObjectIndex = 0; CreatedObjectIndex < CreatedObjects.Num(); ++CreatedObjectIndex)
{
const TWeakObjectPtr<UObject>& 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<UObject*> 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<UPackage*> 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<FContentBrowserModule>("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<UObject*>& OutNewAssets)
{
TArray<void*> 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<FbxNode*> 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<UStaticMesh>(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<UStaticMesh>(CreatedObject);
if(NewStaticMesh->IsSourceModelValid(LODIndex))
{
NewStaticMesh->GetSourceModel(LODIndex).bImportWithBaseMesh = true;
}
}
}
}
}
if (CreatedObject)
{
UStaticMesh *NewStaticMesh = Cast<UStaticMesh>(CreatedObject);
if (NewStaticMesh != nullptr)
{
//Reorder the material
TArray<FbxNode*> 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<UStaticMesh>(CreatedObject);
if (NewStaticMesh != nullptr)
{
//Reorder the material
TArray<FbxNode*> 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; ChildIndex<Node->GetChildCount(); ++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<FString> UFbxFactory::GetFormats() const
{
return GetFbxFormats(this);
}
TArray<FString> UFbxFactory::GetFbxFormats(const UFactory* Factory)
{
TArray<FString> 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<UFbxStaticMeshImportData>(TEXT("StaticMeshImportData"));
StaticMeshImportData->SetFlags(RF_Transactional);
UFbxImportUI::LoadOptions(StaticMeshImportData);
SkeletalMeshImportData = CreateDefaultSubobject<UFbxSkeletalMeshImportData>(TEXT("SkeletalMeshImportData"));
SkeletalMeshImportData->SetFlags(RF_Transactional);
UFbxImportUI::LoadOptions(SkeletalMeshImportData);
AnimSequenceImportData = CreateDefaultSubobject<UFbxAnimSequenceImportData>(TEXT("AnimSequenceImportData"));
AnimSequenceImportData->SetFlags(RF_Transactional);
UFbxImportUI::LoadOptions(AnimSequenceImportData);
TextureImportData = CreateDefaultSubobject<UFbxTextureImportData>(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<class FJsonObject> 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<FJsonObject>* StaticMeshImportJson = nullptr;
ImportSettingsJson->TryGetObjectField(TEXT("StaticMeshImportData"), StaticMeshImportJson);
if(StaticMeshImportJson)
{
FJsonObjectConverter::JsonObjectToUStruct(StaticMeshImportJson->ToSharedRef(), StaticMeshImportData->GetClass(), StaticMeshImportData, 0, 0);
}
const TSharedPtr<FJsonObject>* SkeletalMeshImportJson = nullptr;
ImportSettingsJson->TryGetObjectField(TEXT("SkeletalMeshImportData"), SkeletalMeshImportJson);
if (SkeletalMeshImportJson)
{
FJsonObjectConverter::JsonObjectToUStruct(SkeletalMeshImportJson->ToSharedRef(), SkeletalMeshImportData->GetClass(), SkeletalMeshImportData, 0, 0);
}
const TSharedPtr<FJsonObject>* AnimImportJson = nullptr;
ImportSettingsJson->TryGetObjectField(TEXT("AnimSequenceImportData"), AnimImportJson);
if (AnimImportJson)
{
FJsonObjectConverter::JsonObjectToUStruct(AnimImportJson->ToSharedRef(), AnimSequenceImportData->GetClass(), AnimSequenceImportData, 0, 0);
}
const TSharedPtr<FJsonObject>* 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<FbxNode*>& MeshNodes, FMaterialCompareData& MaterialCompareData)
{
TArray<FName> NodeMaterialNames;
for (int32 NodeIndex = 0; NodeIndex < MeshNodes.Num(); ++NodeIndex)
{
FbxNode* Node = MeshNodes[NodeIndex];
if (Node->GetMesh() == nullptr)
{
continue;
}
int32 MaterialCount = Node->GetMaterialCount();
TArray<int32> 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<FbxNode*>& SkeletalMeshNodes, FSkeletonCompareData& SkeletonCompareData)
{
TArray<FbxNode *> 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<FbxCluster*> 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<FbxNode*>& 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<FbxNode*> FbxMeshArray;
bool bImportStaticMeshLODs = ImportUI->StaticMeshImportData->bImportMeshLODs;
bool bCombineMeshes = ImportUI->StaticMeshImportData->bCombineMeshes;
bool bCombineMeshesLOD = false;
TArray<TArray<FbxNode*>> 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<FbxNode*> 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<FbxNode*> AllNodeInLod;
FFbxImporter->FindAllLODGroupNode(AllNodeInLod, LODGroup, GroupLodIndex);
if (AllNodeInLod.Num() > 0)
{
if (FbxMeshesLod.Num() <= GroupLodIndex)
{
FbxMeshesLod.Add(AllNodeInLod);
}
else
{
TArray<FbxNode*> &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<FbxNode*> 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<FbxNode*> 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<TArray<FbxNode*>*> SkeletalMeshArray;
FFbxImporter->FillFbxSkelMeshArrayInScene(SceneRoot, SkeletalMeshArray, false, (bImportGeoOnly || bImportSkinningOnly), false);
if (SkeletalMeshArray.Num() == 0)
{
return;
}
TArray<FbxNode*>* 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<FbxNode*>*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<FbxNode*>& SkeletalMeshNodes = *(FbxNodes);
if (SkeletalMeshNodes.Num() == 0)
{
return;
}
//Materials
if (!bImportSkinningOnly)
{
//Fill the currrent asset data
const TArray<FSkeletalMaterial>& 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<FbxNode*> 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<UStaticMesh>(ReimportMesh);
USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(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<FArrayProperty>(Property);
if (Array)
{
const FConfigSection* Sec = GConfig->GetSection(*Section, 0, *GEditorPerProjectIni);
if (Sec != nullptr)
{
TArray<FConfigValue> 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<uint8>(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<FArrayProperty>(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