void ImportSkeletalMeshLOD( class USkeletalMesh* SelectedSkelMesh, const FString& Filename, int32 LODLevel ) { // Check the file extension for FBX. Anything that isn't .FBX is rejected const FString FileExtension = FPaths::GetExtension(Filename); const bool bIsFBX = FCString::Stricmp(*FileExtension, TEXT("FBX")) == 0; if (bIsFBX) { #if WITH_APEX_CLOTHING FClothingBackup ClothingBackup; if(LODLevel == 0) { ApexClothingUtils::BackupClothingDataFromSkeletalMesh(SelectedSkelMesh, ClothingBackup); } #endif// #if WITH_APEX_CLOTHING UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance(); // don't import material and animation UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions(); ImportOptions->bImportMaterials = false; ImportOptions->bImportTextures = false; ImportOptions->bImportAnimations = false; if ( !FFbxImporter->ImportFromFile( *Filename, FPaths::GetExtension( Filename ) ) ) { // Log the error message and fail the import. FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FBXImport_ParseFailed", "FBX file parsing failed.")), FFbxErrors::Generic_FBXFileParseFailed); } else { bool bUseLODs = true; int32 MaxLODLevel = 0; TArray< TArray<FbxNode*>* > MeshArray; TArray<FString> LODStrings; TArray<FbxNode*>* MeshObject = NULL;; // Populate the mesh array FFbxImporter->FillFbxSkelMeshArrayInScene(FFbxImporter->Scene->GetRootNode(), MeshArray, false); // Nothing found, error out if (MeshArray.Num() == 0) { FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FBXImport_NoMesh", "No meshes were found in file.")), FFbxErrors::Generic_MeshNotFound); FFbxImporter->ReleaseScene(); return; } MeshObject = MeshArray[0]; // check if there is LODGroup for this skeletal mesh for (int32 j = 0; j < MeshObject->Num(); j++) { FbxNode* Node = (*MeshObject)[j]; if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { // get max LODgroup level if (MaxLODLevel < (Node->GetChildCount() - 1)) { MaxLODLevel = Node->GetChildCount() - 1; } } } // No LODs found, switch to supporting a mesh array containing meshes instead of LODs if (MaxLODLevel == 0) { bUseLODs = false; MaxLODLevel = SelectedSkelMesh->LODInfo.Num(); } // Create LOD dropdown strings LODStrings.AddZeroed(MaxLODLevel + 1); LODStrings[0] = FString::Printf( TEXT("Base") ); for(int32 i = 1; i < MaxLODLevel + 1; i++) { LODStrings[i] = FString::Printf(TEXT("%d"), i); } int32 SelectedLOD = LODLevel; if (SelectedLOD > SelectedSkelMesh->LODInfo.Num()) { // Make sure they don't manage to select a bad LOD index FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("FBXImport_InvalidLODIdx", "Invalid mesh LOD index {0}, no prior LOD index exists"), FText::AsNumber(SelectedLOD))), FFbxErrors::Generic_Mesh_LOD_InvalidIndex); } else { TArray<FbxNode*> SkelMeshNodeArray; if (bUseLODs || ImportOptions->bImportMorph) { for (int32 j = 0; j < MeshObject->Num(); j++) { FbxNode* Node = (*MeshObject)[j]; if (Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup) { if (Node->GetChildCount() > SelectedLOD) { SkelMeshNodeArray.Add(Node->GetChild(SelectedLOD)); } else // in less some LODGroups have less level, use the last level { SkelMeshNodeArray.Add(Node->GetChild(Node->GetChildCount() - 1)); } } else { SkelMeshNodeArray.Add(Node); } } } // Import mesh USkeletalMesh* TempSkelMesh = NULL; // @todo AssetImportData does this temp skeletal mesh need import data? UFbxSkeletalMeshImportData* TempAssetImportData = NULL; TempSkelMesh = (USkeletalMesh*)FFbxImporter->ImportSkeletalMesh(GetTransientPackage(), bUseLODs? SkelMeshNodeArray: *MeshObject, NAME_None, (EObjectFlags)0, TempAssetImportData); // Add imported mesh to existing model bool bImportSucceeded = false; if( TempSkelMesh ) { bImportSucceeded = FFbxImporter->ImportSkeletalMeshLOD(TempSkelMesh, SelectedSkelMesh, SelectedLOD); // Mark package containing skeletal mesh as dirty. SelectedSkelMesh->MarkPackageDirty(); } if(ImportOptions->bImportMorph) { FFbxImporter->ImportFbxMorphTarget(SkelMeshNodeArray, SelectedSkelMesh, SelectedSkelMesh->GetOutermost(), SelectedLOD); } if (bImportSucceeded) { // Notification of success FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(NSLOCTEXT("UnrealEd", "LODImportSuccessful", "Mesh for LOD {0} imported successfully!"), FText::AsNumber(SelectedLOD)); NotificationInfo.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(NotificationInfo); } else { // Notification of failure FNotificationInfo NotificationInfo(FText::GetEmpty()); NotificationInfo.Text = FText::Format(NSLOCTEXT("UnrealEd", "LODImportFail", "Failed to import mesh for LOD {0}!"), FText::AsNumber(SelectedLOD)); NotificationInfo.ExpireDuration = 5.0f; FSlateNotificationManager::Get().AddNotification(NotificationInfo); } } // Cleanup for (int32 i=0; i<MeshArray.Num(); i++) { delete MeshArray[i]; } } FFbxImporter->ReleaseScene(); #if WITH_APEX_CLOTHING if(LODLevel == 0) { ApexClothingUtils::ReapplyClothingDataToSkeletalMesh(SelectedSkelMesh, ClothingBackup); } ApexClothingUtils::ReImportClothingSectionsFromClothingAsset(SelectedSkelMesh); #endif// #if WITH_APEX_CLOTHING } }
UObject* UFbxFactory::FactoryCreateBinary ( UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, const TCHAR* Type, const uint8*& Buffer, const uint8* BufferEnd, FFeedbackContext* Warn, bool& bOutOperationCanceled ) { if( bOperationCanceled ) { bOutOperationCanceled = true; FEditorDelegates::OnAssetPostImport.Broadcast(this, NULL); return NULL; } FEditorDelegates::OnAssetPreImport.Broadcast(this, Class, InParent, Name, Type); UObject* NewObject = NULL; if ( bDetectImportTypeOnImport ) { if ( !DetectImportType(UFactory::CurrentFilename) ) { // Failed to read the file info, fail the import FEditorDelegates::OnAssetPostImport.Broadcast(this, NULL); return NULL; } } // logger for all error/warnings // this one prints all messages that are stored in FFbxImporter UnFbx::FFbxImporter* FbxImporter = UnFbx::FFbxImporter::GetInstance(); UnFbx::FFbxLoggerSetter Logger(FbxImporter); EFBXImportType ForcedImportType = FBXIT_StaticMesh; bool bIsObjFormat = false; if( FString(Type).Equals(TEXT("obj"), ESearchCase::IgnoreCase ) ) { bIsObjFormat = true; } bool bShowImportDialog = bShowOption && !GIsAutomationTesting; bool bImportAll = false; UnFbx::FBXImportOptions* ImportOptions = GetImportOptions(FbxImporter, ImportUI, bShowImportDialog, InParent->GetPathName(), bOperationCanceled, bImportAll, bIsObjFormat, bIsObjFormat, ForcedImportType ); bOutOperationCanceled = bOperationCanceled; 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; } // For multiple files, use the same settings bDetectImportTypeOnImport = false; if (ImportOptions) { Warn->BeginSlowTask( NSLOCTEXT("FbxFactory", "BeginImportingFbxMeshTask", "Importing FBX mesh"), true ); if ( !FbxImporter->ImportFromFile( *UFactory::CurrentFilename, Type ) ) { // 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->bCombineMeshes; if ( ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh ) { FbxImporter->FillFbxSkelMeshArrayInScene(RootNodeToImport, SkelMeshArray, false); InterestingNodeCount = SkelMeshArray.Num(); } else if( 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; 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 ) { bCombineMeshes = NumLODGroups == 0; } } } 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; UStaticMesh* NewStaticMesh = NULL; if ( ImportUI->MeshTypeToImport == FBXIT_StaticMesh ) // static mesh { 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); } ImportedMeshCount = NewStaticMesh ? 1 : 0; } else { TArray<UObject*> AllNewAssets; UObject* Object = RecursiveImportNode(FbxImporter,RootNodeToImport,InParent,Name,Flags,NodeIndex,InterestingNodeCount, AllNewAssets); 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(); AssetIt; ++AssetIt ) { UObject* Asset = *AssetIt; if ( Asset != NewStaticMesh ) { FAssetRegistryModule::AssetCreated(Asset); Asset->MarkPackageDirty(); } } ImportedMeshCount = AllNewAssets.Num(); } // Importing static mesh sockets only works if one mesh is being imported if( ImportedMeshCount == 1 && NewStaticMesh ) { FbxImporter->ImportStaticMeshSockets( NewStaticMesh ); } NewObject = NewStaticMesh; } else if ( ImportUI->MeshTypeToImport == FBXIT_SkeletalMesh )// skeletal mesh { int32 TotalNumNodes = 0; for (int32 i = 0; i < SkelMeshArray.Num(); i++) { TArray<FbxNode*> NodeArray = *SkelMeshArray[i]; TotalNumNodes += NodeArray.Num(); // check if there is LODGroup for this skeletal mesh int32 MaxLODLevel = 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 (MaxLODLevel < Node->GetChildCount()) { MaxLODLevel = Node->GetChildCount(); } } } int32 LODIndex; bool bImportSkeletalMeshLODs = ImportUI->SkeletalMeshImportData->bImportMeshLODs; for (LODIndex = 0; LODIndex < MaxLODLevel; LODIndex++) { 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) { if (Node->GetChildCount() > LODIndex) { SkelMeshNodeArray.Add(Node->GetChild(LODIndex)); } else // in less some LODGroups have less level, use the last level { SkelMeshNodeArray.Add(Node->GetChild(Node->GetChildCount() - 1)); } } else { SkelMeshNodeArray.Add(Node); } } if (LODIndex == 0 && SkelMeshNodeArray.Num() != 0) { FName OutputName = FbxImporter->MakeNameForMesh(Name.ToString(), SkelMeshNodeArray[0]); USkeletalMesh* NewMesh = FbxImporter->ImportSkeletalMesh( InParent, SkelMeshNodeArray, OutputName, Flags, ImportUI->SkeletalMeshImportData, &bOperationCanceled ); NewObject = NewMesh; if(bOperationCanceled) { // User cancelled, clean up and return FbxImporter->ReleaseScene(); Warn->EndSlowTask(); bOperationCanceled = true; return nullptr; } if ( NewMesh && ImportUI->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(NewMesh, InParent, SkelMeshNodeArray, ImportUI->AnimSequenceImportData, OutputName.ToString()); // Reapply the transforms for the rest of the import FbxImporter->ApplyTransformSettingsToFbxNode(RootNodeToImport, ImportUI->SkeletalMeshImportData); } } else if (NewObject) // the base skeletal mesh is imported successfully { USkeletalMesh* BaseSkeletalMesh = Cast<USkeletalMesh>(NewObject); FName LODObjectName = NAME_None; USkeletalMesh *LODObject = FbxImporter->ImportSkeletalMesh( GetTransientPackage(), SkelMeshNodeArray, LODObjectName, RF_NoFlags, ImportUI->SkeletalMeshImportData, &bOperationCanceled ); bool bImportSucceeded = !bOperationCanceled && FbxImporter->ImportSkeletalMeshLOD(LODObject, BaseSkeletalMesh, LODIndex, false); if (bImportSucceeded) { BaseSkeletalMesh->LODInfo[LODIndex].ScreenSize = 1.0f / (MaxLODLevel * LODIndex); } else { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_SkeletalMeshLOD", "Failed to import Skeletal mesh LOD.")), FFbxErrors::SkeletalMesh_LOD_FailedToImport); } } // import morph target if ( NewObject && ImportUI->SkeletalMeshImportData->bImportMorphTargets) { // Disable material importing when importing morph targets uint32 bImportMaterials = ImportOptions->bImportMaterials; ImportOptions->bImportMaterials = 0; FbxImporter->ImportFbxMorphTarget(SkelMeshNodeArray, Cast<USkeletalMesh>(NewObject), InParent, LODIndex); ImportOptions->bImportMaterials = !!bImportMaterials; } } if (NewObject) { NodeIndex++; FFormatNamedArguments Args; Args.Add( TEXT("NodeIndex"), NodeIndex ); Args.Add( TEXT("ArrayLength"), SkelMeshArray.Num() ); GWarn->StatusUpdate( NodeIndex, SkelMeshArray.Num(), FText::Format( NSLOCTEXT("UnrealEd", "Importingf", "Importing ({NodeIndex} of {ArrayLength})"), Args ) ); } } 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 ( ImportUI->MeshTypeToImport == FBXIT_Animation )// animation { if (ImportOptions->SkeletonForAnimation) { // will return the last animation sequence that were added NewObject = 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 (NewObject == NULL) { FbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("FailedToImport_NoObject", "Import failed.")), FFbxErrors::Generic_ImportingNewObjectFailed); } FbxImporter->ReleaseScene(); Warn->EndSlowTask(); } FEditorDelegates::OnAssetPostImport.Broadcast(this, NewObject); return NewObject; }