You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
The majority of asset reimport machinery is now held within UAssetImportData and FAssetImportInfo. This change allows us to remove the CachedAssetsBySourceFileName map from FAssetRegistry that was previously added to support auto reimport for 4.7. The functionality of CachedAssetsBySourceFileName has been wrapped up in FAssetSourceFilenameCache, which sits on top of the asset registry in the editor. This unification allows us to also consistently store the file timestamps and MD5 hashes of imported files. This subsequently allows auto-reimports to be ignored where a source content file has not actually changed (its MD5 is the same). This addresses UETOOL-365 - Rework AssetRegistry changes to support auto-reimport [CL 2567286 by Andrew Rodham in Main branch]
426 lines
14 KiB
C++
426 lines
14 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "UnrealEd.h"
|
|
#include "StaticMeshResources.h"
|
|
#include "Factories.h"
|
|
|
|
#include "FbxMeshUtils.h"
|
|
#include "FbxExporter.h"
|
|
|
|
#include "MainFrame.h"
|
|
#include "DesktopPlatformModule.h"
|
|
|
|
#if WITH_APEX_CLOTHING
|
|
#include "ApexClothingUtils.h"
|
|
#endif // #if WITH_APEX_CLOTHING
|
|
|
|
#include "ComponentReregisterContext.h"
|
|
|
|
#include "FbxErrors.h"
|
|
#include "SNotificationList.h"
|
|
#include "NotificationManager.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogExportMeshUtils, Log, All);
|
|
|
|
#define LOCTEXT_NAMESPACE "FbxMeshUtil"
|
|
|
|
namespace FbxMeshUtils
|
|
{
|
|
/** Helper function used for retrieving data required for importing static mesh LODs */
|
|
void PopulateFBXStaticMeshLODList(UnFbx::FFbxImporter* FFbxImporter, FbxNode* Node, TArray< TArray<FbxNode*>* >& LODNodeList, int32& MaxLODCount, bool bUseLODs)
|
|
{
|
|
// Check for LOD nodes, if one is found, add it to the list
|
|
if (bUseLODs && Node->GetNodeAttribute() && Node->GetNodeAttribute()->GetAttributeType() == FbxNodeAttribute::eLODGroup)
|
|
{
|
|
for (int32 ChildIdx = 0; ChildIdx < Node->GetChildCount(); ++ChildIdx)
|
|
{
|
|
if ((LODNodeList.Num() - 1) < ChildIdx)
|
|
{
|
|
TArray<FbxNode*>* NodeList = new TArray<FbxNode*>;
|
|
LODNodeList.Add(NodeList);
|
|
}
|
|
|
|
LODNodeList[ChildIdx]->Add(Node->GetChild(ChildIdx));
|
|
}
|
|
|
|
if (MaxLODCount < (Node->GetChildCount() - 1))
|
|
{
|
|
MaxLODCount = Node->GetChildCount() - 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we're just looking for meshes instead of LOD nodes, add those to the list
|
|
if (!bUseLODs && Node->GetMesh())
|
|
{
|
|
if (LODNodeList.Num() == 0)
|
|
{
|
|
TArray<FbxNode*>* NodeList = new TArray<FbxNode*>;
|
|
LODNodeList.Add(NodeList);
|
|
}
|
|
|
|
LODNodeList[0]->Add(Node);
|
|
}
|
|
|
|
// Recursively examine child nodes
|
|
for (int32 ChildIndex = 0; ChildIndex < Node->GetChildCount(); ++ChildIndex)
|
|
{
|
|
PopulateFBXStaticMeshLODList(FFbxImporter, Node->GetChild(ChildIndex), LODNodeList, MaxLODCount, bUseLODs);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImportStaticMeshLOD( UStaticMesh* BaseStaticMesh, const FString& Filename, int32 LODLevel )
|
|
{
|
|
UE_LOG(LogExportMeshUtils, Log, TEXT("Fbx LOD loading"));
|
|
// logger for all error/warnings
|
|
// this one prints all messages that are stored in FFbxImporter
|
|
// this function seems to get called outside of FBX factory
|
|
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
UnFbx::FFbxLoggerSetter Logger(FFbxImporter);
|
|
|
|
// don't import materials
|
|
UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions();
|
|
ImportOptions->bImportMaterials = false;
|
|
ImportOptions->bImportTextures = false;
|
|
|
|
if ( !FFbxImporter->ImportFromFile( *Filename, FPaths::GetExtension( Filename ) ) )
|
|
{
|
|
// Log the error message and fail the import.
|
|
// @todo verify if the message works
|
|
FFbxImporter->FlushToTokenizedErrorMessage(EMessageSeverity::Error);
|
|
}
|
|
else
|
|
{
|
|
FFbxImporter->FlushToTokenizedErrorMessage(EMessageSeverity::Warning);
|
|
|
|
bool bUseLODs = true;
|
|
int32 MaxLODLevel = 0;
|
|
TArray< TArray<FbxNode*>* > LODNodeList;
|
|
TArray<FString> LODStrings;
|
|
|
|
// Create a list of LOD nodes
|
|
PopulateFBXStaticMeshLODList(FFbxImporter, FFbxImporter->Scene->GetRootNode(), LODNodeList, MaxLODLevel, bUseLODs);
|
|
|
|
// No LODs, so just grab all of the meshes in the file
|
|
if (MaxLODLevel == 0)
|
|
{
|
|
bUseLODs = false;
|
|
MaxLODLevel = BaseStaticMesh->GetNumLODs();
|
|
|
|
// Create a list of meshes
|
|
PopulateFBXStaticMeshLODList(FFbxImporter, FFbxImporter->Scene->GetRootNode(), LODNodeList, MaxLODLevel, bUseLODs);
|
|
|
|
// Nothing found, error out
|
|
if (LODNodeList.Num() == 0)
|
|
{
|
|
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, FText(LOCTEXT("Prompt_NoMeshFound", "No meshes were found in file."))), FFbxErrors::Generic_Mesh_MeshNotFound);
|
|
|
|
FFbxImporter->ReleaseScene();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Display the LOD selection dialog
|
|
if (LODLevel > BaseStaticMesh->GetNumLODs())
|
|
{
|
|
// Make sure they don't manage to select a bad LOD index
|
|
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Warning, FText::Format(LOCTEXT("Prompt_InvalidLODIndex", "Invalid mesh LOD index {0}, as no prior LOD index exists!"), FText::AsNumber(LODLevel))), FFbxErrors::Generic_Mesh_LOD_InvalidIndex);
|
|
}
|
|
else
|
|
{
|
|
// Import mesh
|
|
UStaticMesh* TempStaticMesh = NULL;
|
|
TempStaticMesh = (UStaticMesh*)FFbxImporter->ImportStaticMeshAsSingle(GetTransientPackage(), *(LODNodeList[bUseLODs? LODLevel: 0]), NAME_None, RF_NoFlags, NULL, BaseStaticMesh, LODLevel);
|
|
|
|
// Add imported mesh to existing model
|
|
if( TempStaticMesh )
|
|
{
|
|
// Update mesh component
|
|
BaseStaticMesh->MarkPackageDirty();
|
|
|
|
// Import worked
|
|
FNotificationInfo NotificationInfo(FText::GetEmpty());
|
|
NotificationInfo.Text = FText::Format(LOCTEXT("LODImportSuccessful", "Mesh for LOD {0} imported successfully!"), FText::AsNumber(LODLevel));
|
|
NotificationInfo.ExpireDuration = 5.0f;
|
|
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
|
|
}
|
|
else
|
|
{
|
|
// Import failed
|
|
FNotificationInfo NotificationInfo(FText::GetEmpty());
|
|
NotificationInfo.Text = FText::Format(LOCTEXT("LODImportFail", "Failed to import mesh for LOD {0}!"), FText::AsNumber( LODLevel ));
|
|
NotificationInfo.ExpireDuration = 5.0f;
|
|
FSlateNotificationManager::Get().AddNotification(NotificationInfo);
|
|
}
|
|
}
|
|
|
|
// Cleanup
|
|
for (int32 i = 0; i < LODNodeList.Num(); ++i)
|
|
{
|
|
delete LODNodeList[i];
|
|
}
|
|
}
|
|
FFbxImporter->ReleaseScene();
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
void ImportMeshLODDialog( class UObject* SelectedMesh, int32 LODLevel )
|
|
{
|
|
if(!SelectedMesh)
|
|
{
|
|
return;
|
|
}
|
|
|
|
USkeletalMesh* SkeletonMesh = Cast<USkeletalMesh>(SelectedMesh);
|
|
UStaticMesh* StaticMesh = Cast<UStaticMesh>(SelectedMesh);
|
|
|
|
if( !SkeletonMesh && !StaticMesh )
|
|
{
|
|
return;
|
|
}
|
|
|
|
FString ExtensionStr;
|
|
|
|
ExtensionStr += TEXT("All model files|*.fbx;*.obj|");
|
|
|
|
ExtensionStr += TEXT("FBX files|*.fbx|");
|
|
|
|
ExtensionStr += TEXT("Object files|*.obj|");
|
|
|
|
ExtensionStr += TEXT("All files|*.*");
|
|
|
|
// First, display the file open dialog for selecting the file.
|
|
TArray<FString> OpenFilenames;
|
|
IDesktopPlatform* DesktopPlatform = FDesktopPlatformModule::Get();
|
|
bool bOpen = false;
|
|
if ( DesktopPlatform )
|
|
{
|
|
void* ParentWindowWindowHandle = NULL;
|
|
|
|
IMainFrameModule& MainFrameModule = FModuleManager::LoadModuleChecked<IMainFrameModule>(TEXT("MainFrame"));
|
|
const TSharedPtr<SWindow>& MainFrameParentWindow = MainFrameModule.GetParentWindow();
|
|
if ( MainFrameParentWindow.IsValid() && MainFrameParentWindow->GetNativeWindow().IsValid() )
|
|
{
|
|
ParentWindowWindowHandle = MainFrameParentWindow->GetNativeWindow()->GetOSWindowHandle();
|
|
}
|
|
|
|
bOpen = DesktopPlatform->OpenFileDialog(
|
|
ParentWindowWindowHandle,
|
|
FText::Format( NSLOCTEXT("UnrealEd", "ImportMeshLOD", "Failed to import mesh for LOD {0}!"), FText::AsNumber( LODLevel ) ).ToString(),
|
|
*FEditorDirectories::Get().GetLastDirectory(ELastDirectory::FBX),
|
|
TEXT(""),
|
|
*ExtensionStr,
|
|
EFileDialogFlags::None,
|
|
OpenFilenames
|
|
);
|
|
}
|
|
|
|
// Only continue if we pressed OK and have only one file selected.
|
|
if( bOpen )
|
|
{
|
|
if( OpenFilenames.Num() == 0)
|
|
{
|
|
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("NoFileSelectedForLOD", "No file was selected for the LOD.")), FFbxErrors::Generic_Mesh_LOD_NoFileSelected);
|
|
}
|
|
else if(OpenFilenames.Num() > 1)
|
|
{
|
|
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
FFbxImporter->AddTokenizedErrorMessage(FTokenizedMessage::Create(EMessageSeverity::Error, LOCTEXT("MultipleFilesSelectedForLOD", "You may only select one file for the LOD.")), FFbxErrors::Generic_Mesh_LOD_MultipleFilesSelected);
|
|
}
|
|
else
|
|
{
|
|
FString Filename = OpenFilenames[0];
|
|
FEditorDirectories::Get().SetLastDirectory(ELastDirectory::FBX, FPaths::GetPath(Filename)); // Save path as default for next time.
|
|
|
|
if( SkeletonMesh )
|
|
{
|
|
ImportSkeletalMeshLOD(SkeletonMesh, Filename, LODLevel);
|
|
}
|
|
else if( StaticMesh )
|
|
{
|
|
ImportStaticMeshLOD(StaticMesh, Filename, LODLevel);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void SetImportOption(UFbxImportUI* ImportUI)
|
|
{
|
|
UnFbx::FFbxImporter* FFbxImporter = UnFbx::FFbxImporter::GetInstance();
|
|
UnFbx::FBXImportOptions* ImportOptions = FFbxImporter->GetImportOptions();
|
|
ApplyImportUIToImportOptions(ImportUI, *ImportOptions);
|
|
}
|
|
} //end namespace MeshUtils
|
|
|
|
#undef LOCTEXT_NAMESPACE |