Files
UnrealEngineUWP/Engine/Source/Developer/Datasmith/DatasmithExporter/Private/DatasmithSceneExporter.cpp
benoit deschenes afde922bf1 DatasmithExporter - Fixing Rhino and Revit broken export (no export in Rhino, bad mesh scaling in Rhino & Revit)
- Restoring previous FDatasmithSceneExporter::Reset() behavior but marking the function as deprecated.
It's previous usage of reseting the export progress without resetting the whole exporter state was discutable and confusing. If users want to fully reset the exporter they don't need the Reset() function.

- Fixing Datasmith Facade inverse scaling error during position conversion

#preflight 624b48d9dc6183e3f547d3b1
#rb Johan.Duparc Kerim.Borchaev

[CL 19625080 by benoit deschenes in ue5-main branch]
2022-04-05 10:39:18 -04:00

360 lines
11 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DatasmithSceneExporter.h"
#include "DatasmithAnimationSerializer.h"
#include "DatasmithExportOptions.h"
#include "DatasmithExporterManager.h"
#include "DatasmithLogger.h"
#include "DatasmithMaterialElements.h"
#include "DatasmithProgressManager.h"
#include "DatasmithSceneFactory.h"
#include "DatasmithSceneXmlWriter.h"
#include "DatasmithUtils.h"
#include "GenericPlatform/GenericPlatformFile.h"
#include "Containers/Array.h"
#include "Algo/Find.h"
#include "Containers/Set.h"
#include "HAL/FileManager.h"
#include "HAL/PlatformFileManager.h"
#include "Math/UnrealMathUtility.h"
#include "Misc/Paths.h"
#include "Misc/SecureHash.h"
#include "UObject/GarbageCollection.h"
// Hash function to use FMD5Hash in TMap
inline uint32 GetTypeHash(const FMD5Hash& Hash)
{
uint32* HashAsInt32 = (uint32*)Hash.GetBytes();
return HashAsInt32[0] ^ HashAsInt32[1] ^ HashAsInt32[2] ^ HashAsInt32[3];
}
class FDatasmithSceneExporterImpl
{
public:
FDatasmithSceneExporterImpl();
// call this before export the actual bitmaps
void CheckBumpMaps( TSharedRef< IDatasmithScene > DatasmithScene );
void UpdateTextureElements( TSharedRef< IDatasmithScene > DatasmithScene );
void UpdateAssetOutputPath();
static EDatasmithTextureMode GetTextureModeFromPropertyName(const FString& PropertyName);
static FString GetFileNameWithHash(const FString& FullPath);
FString Name;
FString OutputPath;
FString AssetsOutputPath;
uint64 ExportStartCycles;
TSharedPtr<IDatasmithProgressManager> ProgressManager;
TSharedPtr<FDatasmithLogger> Logger;
};
FDatasmithSceneExporterImpl::FDatasmithSceneExporterImpl()
: ExportStartCycles(0)
{
}
void FDatasmithSceneExporterImpl::UpdateTextureElements( TSharedRef< IDatasmithScene > DatasmithScene )
{
FDatasmithTextureUtils::CalculateTextureHashes(DatasmithScene);
// No need to do anything if user required to keep images at original location or no output path is set
if (FDatasmithExportOptions::PathTexturesMode == EDSResizedTexturesPath::OriginalFolder || AssetsOutputPath.IsEmpty())
{
return;
}
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
TMap<FMD5Hash, FString> HashFilePathMap;
FDatasmithUniqueNameProvider TextureFileNameProvider;
for (int32 i = 0; i < DatasmithScene->GetTexturesCount(); i++)
{
TSharedPtr< IDatasmithTextureElement > TextureElement = DatasmithScene->GetTexture(i);
FString TextureFileName = TextureElement->GetFile();
float RatioDone = float(i + 1) / float( DatasmithScene->GetTexturesCount() );
if (ProgressManager.IsValid())
{
ProgressManager->ProgressEvent(RatioDone, *FPaths::GetBaseFilename( TextureFileName ));
}
FString& NewFilename = HashFilePathMap.FindOrAdd(TextureElement->GetFileHash());
// If this texture has not been exported yet, find a unique name for it and copy its file to the asset output path.
if (NewFilename.IsEmpty())
{
const FString UniqueFileName = TextureFileNameProvider.GenerateUniqueName( FPaths::GetBaseFilename( TextureFileName ) );
TextureFileNameProvider.AddExistingName( UniqueFileName );
const FString FileExtension = FPaths::GetExtension(TextureFileName, /*bIncludeDot=*/true);
NewFilename = FPaths::Combine(AssetsOutputPath, UniqueFileName + FileExtension);
// Copy image file to new location
PlatformFile.CopyFile(*NewFilename, *TextureFileName);
}
// Update texture element
if (TextureFileName != NewFilename)
{
TextureElement->SetFile(*NewFilename);
}
}
}
void FDatasmithSceneExporterImpl::CheckBumpMaps( TSharedRef< IDatasmithScene > DatasmithScene )
{
for ( int32 MaterialIndex = 0; MaterialIndex < DatasmithScene->GetMaterialsCount(); ++MaterialIndex )
{
TSharedPtr< IDatasmithBaseMaterialElement > BaseMaterialElement = DatasmithScene->GetMaterial( MaterialIndex );
if ( BaseMaterialElement->IsA( EDatasmithElementType::Material ) )
{
const TSharedPtr< IDatasmithMaterialElement >& MaterialElement = StaticCastSharedPtr< IDatasmithMaterialElement >( BaseMaterialElement );
for (int32 j = 0; j < MaterialElement->GetShadersCount(); ++j )
{
TSharedPtr< IDatasmithShaderElement >& Shader = MaterialElement->GetShader(j);
if (Shader->GetBumpComp()->GetMode() == EDatasmithCompMode::Regular && Shader->GetBumpComp()->GetParamSurfacesCount() == 1 &&
Shader->GetNormalComp()->GetParamSurfacesCount() == 0)
{
FString TextureName = Shader->GetBumpComp()->GetParamTexture(0);
FString NormalTextureName = TextureName + TEXT("_Norm");
if ( !TextureName.IsEmpty() )
{
FDatasmithTextureSampler UVs = Shader->GetBumpComp()->GetParamTextureSampler(0);
TSharedPtr< IDatasmithTextureElement > TextureElement;
TSharedPtr< IDatasmithTextureElement > NormalTextureElement;
for ( int32 TextureIndex = 0; TextureIndex < DatasmithScene->GetTexturesCount(); ++TextureIndex )
{
if ( DatasmithScene->GetTexture( TextureIndex )->GetName() == TextureName )
{
TextureElement = DatasmithScene->GetTexture( TextureIndex );
}
else if ( DatasmithScene->GetTexture( TextureIndex )->GetName() == NormalTextureName )
{
NormalTextureElement = DatasmithScene->GetTexture( TextureIndex );
}
}
if ( TextureElement )
{
if ( !NormalTextureElement )
{
NormalTextureElement = FDatasmithSceneFactory::CreateTexture( *NormalTextureName );
NormalTextureElement->SetRGBCurve( 1.f );
NormalTextureElement->SetFile( TextureElement->GetFile() );
NormalTextureElement->SetFileHash( TextureElement->GetFileHash() );
NormalTextureElement->SetTextureMode( EDatasmithTextureMode::Bump );
DatasmithScene->AddTexture( NormalTextureElement );
}
Shader->GetNormalComp()->AddSurface( *NormalTextureName, UVs );
Shader->GetBumpComp()->ClearSurface();
}
}
}
}
}
}
}
void FDatasmithSceneExporterImpl::UpdateAssetOutputPath()
{
if (Name.IsEmpty())
{
// Just set the AssetsOutputPath to OutputPath, if the scene exporter has not been named
AssetsOutputPath = OutputPath;
}
else if (!OutputPath.IsEmpty())
{
AssetsOutputPath = FPaths::Combine(OutputPath, Name + TEXT("_Assets"));
}
}
EDatasmithTextureMode FDatasmithSceneExporterImpl::GetTextureModeFromPropertyName(const FString& PropertyName)
{
if (PropertyName.Find(TEXT("BUMP")) != INDEX_NONE)
{
return EDatasmithTextureMode::Bump;
}
else if (PropertyName.Find(TEXT("SPECULAR")) != INDEX_NONE)
{
return EDatasmithTextureMode::Specular;
}
else if (PropertyName.Find(TEXT("NORMAL")) != INDEX_NONE)
{
return EDatasmithTextureMode::Normal;
}
return EDatasmithTextureMode::Diffuse;
};
FString FDatasmithSceneExporterImpl::GetFileNameWithHash(const FString& FullPath)
{
FString Hash = FMD5::HashAnsiString(*FullPath);
FString FileName = FPaths::GetBaseFilename(FullPath);
FString Extension = FPaths::GetExtension(FileName);
FileName = FileName + TEXT("_") + Hash + Extension;
return FileName;
}
FDatasmithSceneExporter::FDatasmithSceneExporter()
: Impl( MakeUnique< FDatasmithSceneExporterImpl >() )
{
}
FDatasmithSceneExporter::~FDatasmithSceneExporter() = default;
void FDatasmithSceneExporter::PreExport()
{
// Collect start time to log amount of time spent to export scene
Impl->ExportStartCycles = FPlatformTime::Cycles64();
}
void FDatasmithSceneExporter::Export( TSharedRef< IDatasmithScene > DatasmithScene, bool bCleanupUnusedElements )
{
if ( Impl->ExportStartCycles == 0 )
{
Impl->ExportStartCycles = FPlatformTime::Cycles64();
}
FString FilePath = FPaths::Combine(Impl->OutputPath, Impl->Name ) + TEXT(".") + FDatasmithUtils::GetFileExtension();
TUniquePtr<FArchive> Archive( IFileManager::Get().CreateFileWriter( *FilePath ) );
if ( !Archive.IsValid() )
{
if ( Impl->Logger.IsValid() )
{
Impl->Logger->AddGeneralError( *( TEXT("Unable to create file ") + FilePath + TEXT(", Aborting the export process") ) );
}
return;
}
IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile();
PlatformFile.CreateDirectoryTree( *Impl->AssetsOutputPath );
// Add Bump maps from Material objects to scene as TextureElement
Impl->CheckBumpMaps( DatasmithScene );
FDatasmithSceneUtils::CleanUpScene(DatasmithScene, bCleanupUnusedElements);
// Update TextureElements
Impl->UpdateTextureElements( DatasmithScene );
// Convert paths to relative
FString AbsoluteDir = Impl->OutputPath + TEXT("/");
for ( int32 MeshIndex = 0; MeshIndex < DatasmithScene->GetMeshesCount(); ++MeshIndex )
{
TSharedPtr< IDatasmithMeshElement > Mesh = DatasmithScene->GetMesh( MeshIndex );
FString RelativePath = Mesh->GetFile();
FPaths::MakePathRelativeTo( RelativePath, *AbsoluteDir );
Mesh->SetFile( *RelativePath );
}
for ( int32 TextureIndex = 0; TextureIndex < DatasmithScene->GetTexturesCount(); ++TextureIndex )
{
TSharedPtr< IDatasmithTextureElement > Texture = DatasmithScene->GetTexture( TextureIndex );
FString TextureFile = Texture->GetFile();
FPaths::MakePathRelativeTo( TextureFile, *AbsoluteDir );
Texture->SetFile( *TextureFile );
}
FDatasmithAnimationSerializer AnimSerializer;
int32 NumSequences = DatasmithScene->GetLevelSequencesCount();
for (int32 SequenceIndex = 0; SequenceIndex < NumSequences; ++SequenceIndex)
{
const TSharedPtr<IDatasmithLevelSequenceElement>& LevelSequence = DatasmithScene->GetLevelSequence(SequenceIndex);
if (LevelSequence.IsValid())
{
FString AnimFilePath = FPaths::Combine(Impl->AssetsOutputPath, LevelSequence->GetName()) + DATASMITH_ANIMATION_EXTENSION;
if (AnimSerializer.Serialize(LevelSequence.ToSharedRef(), *AnimFilePath))
{
TUniquePtr<FArchive> AnimArchive(IFileManager::Get().CreateFileReader(*AnimFilePath));
if (AnimArchive)
{
LevelSequence->SetFileHash(FMD5Hash::HashFileFromArchive(AnimArchive.Get()));
}
FPaths::MakePathRelativeTo(AnimFilePath, *AbsoluteDir);
LevelSequence->SetFile(*AnimFilePath);
}
}
}
// Log time spent to export scene in seconds
int ElapsedTime = (int)FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - Impl->ExportStartCycles);
DatasmithScene->SetExportDuration( ElapsedTime );
FDatasmithSceneXmlWriter DatasmithSceneXmlWriter;
DatasmithSceneXmlWriter.Serialize( DatasmithScene, *Archive );
Archive->Close();
// Run the garbage collector at this point so that we're in a good state for the next export
FDatasmithExporterManager::RunGarbageCollection();
}
void FDatasmithSceneExporter::Reset()
{
Impl->ProgressManager = nullptr;
Impl->Logger = nullptr;
Impl->ExportStartCycles = 0;
}
void FDatasmithSceneExporter::SetProgressManager( const TSharedPtr< IDatasmithProgressManager >& InProgressManager )
{
Impl->ProgressManager = InProgressManager;
}
void FDatasmithSceneExporter::SetLogger( const TSharedPtr< FDatasmithLogger >& InLogger )
{
Impl->Logger = InLogger;
}
void FDatasmithSceneExporter::SetName(const TCHAR* InName)
{
Impl->Name = InName;
Impl->UpdateAssetOutputPath();
}
const TCHAR* FDatasmithSceneExporter::GetName() const
{
return *Impl->Name;
}
void FDatasmithSceneExporter::SetOutputPath( const TCHAR* InOutputPath )
{
Impl->OutputPath = InOutputPath;
FPaths::NormalizeDirectoryName( Impl->OutputPath );
Impl->UpdateAssetOutputPath();
}
const TCHAR* FDatasmithSceneExporter::GetOutputPath() const
{
return *Impl->OutputPath;
}
const TCHAR* FDatasmithSceneExporter::GetAssetsOutputPath() const
{
return *Impl->AssetsOutputPath;
}