485 lines
18 KiB
C++
485 lines
18 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "FbxAPI.h"
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "FbxCamera.h"
|
|
#include "FbxConvert.h"
|
|
#include "FbxHelper.h"
|
|
#include "FbxInclude.h"
|
|
#include "FbxLight.h"
|
|
#include "FbxMaterial.h"
|
|
#include "FbxMesh.h"
|
|
#include "FbxScene.h"
|
|
#include "InterchangeTextureNode.h"
|
|
#if WITH_ENGINE
|
|
#include "Mesh/InterchangeMeshPayload.h"
|
|
#endif
|
|
#include "Misc/Paths.h"
|
|
#include "Nodes/InterchangeBaseNodeContainer.h"
|
|
#include "Nodes/InterchangeSourceNode.h"
|
|
#include "Misc/SecureHash.h"
|
|
#include "InterchangeCommonAnimationPayload.h"
|
|
#include "Serialization/LargeMemoryWriter.h"
|
|
|
|
#include "FbxAnimation.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "InterchangeFbxParser"
|
|
|
|
#define DESTROY_FBX_OBJECT(Object) \
|
|
if(Object) \
|
|
{ \
|
|
Object->Destroy(); \
|
|
Object = nullptr; \
|
|
}
|
|
|
|
namespace UE
|
|
{
|
|
namespace Interchange
|
|
{
|
|
namespace Private
|
|
{
|
|
FFbxParser::~FFbxParser()
|
|
{
|
|
FbxHelper = nullptr;
|
|
Reset();
|
|
}
|
|
|
|
void FFbxParser::Reset()
|
|
{
|
|
PayloadContexts.Reset();
|
|
|
|
DESTROY_FBX_OBJECT(SDKImporter);
|
|
DESTROY_FBX_OBJECT(SDKScene);
|
|
if (SDKGeometryConverter)
|
|
{
|
|
delete SDKGeometryConverter;
|
|
SDKGeometryConverter = nullptr;
|
|
}
|
|
DESTROY_FBX_OBJECT(SDKIoSettings);
|
|
DESTROY_FBX_OBJECT(SDKManager);
|
|
if (FbxHelper.IsValid())
|
|
{
|
|
FbxHelper->Reset();
|
|
}
|
|
}
|
|
|
|
const TSharedPtr<FFbxHelper> FFbxParser::GetFbxHelper()
|
|
{
|
|
if (!FbxHelper.IsValid())
|
|
{
|
|
FbxHelper = MakeShared<FFbxHelper>();
|
|
}
|
|
check(FbxHelper.IsValid());
|
|
return FbxHelper;
|
|
}
|
|
|
|
bool FFbxParser::LoadFbxFile(const FString& Filename, UInterchangeBaseNodeContainer& NodeContainer)
|
|
{
|
|
SourceFilename = Filename;
|
|
int32 SDKMajor, SDKMinor, SDKRevision;
|
|
|
|
//The first thing to do is to create the FBX Manager which is the object allocator for almost all the classes in the SDK
|
|
SDKManager = FbxManager::Create();
|
|
if (!SDKManager)
|
|
{
|
|
UInterchangeResultError_Generic* Message = AddMessage<UInterchangeResultError_Generic>();
|
|
Message->Text = LOCTEXT("CannotCreateFBXManager", "Cannot create FBX SDK manager.");
|
|
return false;
|
|
}
|
|
|
|
//Create an IOSettings object. This object holds all import/export settings.
|
|
SDKIoSettings = FbxIOSettings::Create(SDKManager, IOSROOT);
|
|
SDKIoSettings->SetBoolProp(IMP_FBX_MATERIAL, true);
|
|
SDKIoSettings->SetBoolProp(IMP_FBX_TEXTURE, true);
|
|
SDKIoSettings->SetBoolProp(IMP_FBX_LINK, true);
|
|
SDKIoSettings->SetBoolProp(IMP_FBX_SHAPE, true);
|
|
SDKIoSettings->SetBoolProp(IMP_FBX_GOBO, true);
|
|
SDKIoSettings->SetBoolProp(IMP_FBX_ANIMATION, true);
|
|
SDKIoSettings->SetBoolProp(IMP_SKINS, true);
|
|
SDKIoSettings->SetBoolProp(IMP_DEFORMATION, true);
|
|
SDKIoSettings->SetBoolProp(IMP_FBX_GLOBAL_SETTINGS, true);
|
|
SDKIoSettings->SetBoolProp(IMP_TAKE, true);
|
|
SDKManager->SetIOSettings(SDKIoSettings);
|
|
|
|
SDKGeometryConverter = new FbxGeometryConverter(SDKManager);
|
|
|
|
//Create an FBX scene. This object holds most objects imported/exported from/to files.
|
|
SDKScene = FbxScene::Create(SDKManager, "My Scene");
|
|
if (!SDKScene)
|
|
{
|
|
UInterchangeResultError_Generic* Message = AddMessage<UInterchangeResultError_Generic>();
|
|
Message->Text = LOCTEXT("CannotCreateFBXScene", "Cannot create FBX SDK scene.");
|
|
return false;
|
|
}
|
|
|
|
// Create an importer.
|
|
SDKImporter = FbxImporter::Create(SDKManager, "");
|
|
|
|
// Get the version number of the FBX files generated by the
|
|
// version of FBX SDK that you are using.
|
|
FbxManager::GetFileFormatVersion(SDKMajor, SDKMinor, SDKRevision);
|
|
|
|
// Initialize the importer by providing a filename.
|
|
const bool bImportStatus = SDKImporter->Initialize(TCHAR_TO_UTF8(*Filename));
|
|
if (!bImportStatus)
|
|
{
|
|
FFormatNamedArguments FilenameText
|
|
{
|
|
{ TEXT("Filename"), FText::FromString(Filename) }
|
|
};
|
|
UInterchangeResultError_Generic* Message = AddMessage<UInterchangeResultError_Generic>();
|
|
Message->Text = FText::Format(LOCTEXT("CannotOpenFBXFile", "Cannot open FBX file '{Filename}'."), FilenameText);
|
|
return false;
|
|
}
|
|
|
|
bool bStatus = SDKImporter->Import(SDKScene);
|
|
|
|
//To be able to re-import legacy fbx imported skeleton hierarchy we need to rename bones the same way legacy was doing, so this rename precede the CleanupFbxData renaming
|
|
constexpr bool bRemovePath = true;
|
|
EnsureNodeNameAreValid(FPaths::GetBaseFilename(Filename, bRemovePath));
|
|
|
|
//We always convert scene to UE axis and units
|
|
FbxAMatrix AxisConversionInverseMatrix;
|
|
FFbxConvert::ConvertScene(SDKScene, bConvertScene, bForceFrontXAxis, bConvertSceneUnit, FileDetails.AxisDirection, FileDetails.UnitSystem, AxisConversionInverseMatrix);
|
|
|
|
//Save the AxisConversionInverseTransform into InterchangeSourceNode (so that socket transport can use it accordingly).
|
|
FTransform AxisConversionInverseTransform = FFbxConvert::ConvertTransform<FTransform, FVector, FQuat>(AxisConversionInverseMatrix);
|
|
UInterchangeSourceNode* SourceNode = UInterchangeSourceNode::FindOrCreateUniqueInstance(&NodeContainer);
|
|
SourceNode->SetCustomAxisConversionInverseTransform(AxisConversionInverseTransform);
|
|
|
|
//Store the fbx frame rate
|
|
{
|
|
FrameRate = FbxTime::GetFrameRate(SDKScene->GetGlobalSettings().GetTimeMode());
|
|
FileDetails.FrameRate = FString::Printf(TEXT("%.2f"), FrameRate);
|
|
SourceNode->SetCustomSourceFrameRateNumerator(FrameRate);
|
|
constexpr double Denominator = 1.0;
|
|
SourceNode->SetCustomSourceFrameRateDenominator(Denominator);
|
|
}
|
|
|
|
// Fbx legacy has a special way to bake the skeletal mesh that do not fit the interchange standard
|
|
// The interchange skeletal mesh factory will read this to use the proper bake transform so it match legacy behavior.
|
|
// This fix the issue with blender armature bone skip
|
|
SourceNode->SetCustomUseLegacySkeletalMeshBakeTransform(true);
|
|
|
|
// Get the version number of the FBX file format.
|
|
int32 FileMajor, FileMinor, FileRevision;
|
|
SDKImporter->GetFileVersion(FileMajor, FileMinor, FileRevision);
|
|
FileDetails.FbxFileVersion = FString::Printf(TEXT("%d.%d.%d"), FileMajor, FileMinor, FileRevision);
|
|
|
|
// Get The Creator of the FBX File.
|
|
FileDetails.FbxFileCreator = UTF8_TO_TCHAR(SDKImporter->GetFileHeaderInfo()->mCreator.Buffer());
|
|
{
|
|
//Example of creator file info string
|
|
//Blender (stable FBX IO) - 2.78 (sub 0) - 3.7.7
|
|
//Maya and Max use the same string where they specify the fbx sdk version, so we cannot know it is coming from which software
|
|
//We need blender creator when importing skeletal mesh containing the "armature" dummy node as the parent of the root joint. We want to remove this dummy "armature" node
|
|
bCreatorIsBlender = FileDetails.FbxFileCreator.StartsWith(TEXT("Blender"));
|
|
}
|
|
|
|
FbxDocumentInfo* DocInfo = SDKImporter->GetSceneInfo();
|
|
if (DocInfo)
|
|
{
|
|
FString LastSavedVendor(UTF8_TO_TCHAR(DocInfo->LastSaved_ApplicationVendor.Get().Buffer()));
|
|
FString LastSavedAppName(UTF8_TO_TCHAR(DocInfo->LastSaved_ApplicationName.Get().Buffer()));
|
|
FString LastSavedAppVersion(UTF8_TO_TCHAR(DocInfo->LastSaved_ApplicationVersion.Get().Buffer()));
|
|
|
|
FileDetails.ApplicationVendor = LastSavedVendor;
|
|
FileDetails.ApplicationName = LastSavedAppName;
|
|
FileDetails.ApplicationVersion = LastSavedAppVersion;
|
|
}
|
|
else
|
|
{
|
|
FileDetails.ApplicationVendor = TEXT("");
|
|
FileDetails.ApplicationName = TEXT("");
|
|
FileDetails.ApplicationVersion = TEXT("");
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FFbxParser::FillContainerWithFbxScene(UInterchangeBaseNodeContainer& NodeContainer)
|
|
{
|
|
CleanupFbxData();
|
|
|
|
FFbxMaterial FbxMaterial(*this);
|
|
FbxMaterial.AddAllTextures(SDKScene, NodeContainer);
|
|
FbxMaterial.AddAllMaterials(SDKScene, NodeContainer);
|
|
|
|
FFbxMesh FbxMesh(*this);
|
|
FbxMesh.AddAllMeshes(SDKScene, SDKGeometryConverter, NodeContainer, PayloadContexts);
|
|
|
|
FFbxLight FbxLight(*this);
|
|
FbxLight.AddAllLights(SDKScene, NodeContainer);
|
|
|
|
FFbxCamera FbxCamera(*this);
|
|
FbxCamera.AddAllCameras(SDKScene, NodeContainer);
|
|
|
|
FFbxScene FbxScene(*this);
|
|
FbxScene.AddHierarchy(SDKScene, NodeContainer, PayloadContexts);
|
|
FbxScene.AddAnimation(SDKScene, NodeContainer, PayloadContexts);
|
|
FbxScene.AddMorphTargetAnimations(SDKScene, NodeContainer, PayloadContexts, FbxMesh.GetMorphTargetAnimationsBuildingData());
|
|
|
|
ProcessExtraInformation(NodeContainer);
|
|
}
|
|
|
|
bool FFbxParser::FetchPayloadData(const FString& PayloadKey, const FString& PayloadFilepath)
|
|
{
|
|
if (!PayloadContexts.Contains(PayloadKey))
|
|
{
|
|
UInterchangeResultError_Generic* Message = AddMessage<UInterchangeResultError_Generic>();
|
|
Message->Text = LOCTEXT("CannotRetrievePayload", "Cannot retrieve payload; payload key doesn't have any context.");
|
|
return false;
|
|
}
|
|
|
|
{
|
|
//Critical section to force payload to be fetch one by one with no concurrency.
|
|
FScopeLock Lock(&PayloadCriticalSection);
|
|
TSharedPtr<FPayloadContextBase>& PayloadContext = PayloadContexts.FindChecked(PayloadKey);
|
|
return PayloadContext->FetchPayloadToFile(*this, PayloadFilepath);
|
|
}
|
|
}
|
|
|
|
bool FFbxParser::FetchMeshPayloadData(const FString& PayloadKey, const FTransform& MeshGlobalTransform, const FString& PayloadFilepath)
|
|
{
|
|
if (!PayloadContexts.Contains(PayloadKey))
|
|
{
|
|
UInterchangeResultError_Generic* Message = AddMessage<UInterchangeResultError_Generic>();
|
|
Message->Text = LOCTEXT("CannotRetrievePayload", "Cannot retrieve payload; payload key doesn't have any context.");
|
|
return false;
|
|
}
|
|
|
|
{
|
|
//Critical section to force payload to be fetch one by one with no concurrency.
|
|
FScopeLock Lock(&PayloadCriticalSection);
|
|
TSharedPtr<FPayloadContextBase>& PayloadContext = PayloadContexts.FindChecked(PayloadKey);
|
|
return PayloadContext->FetchMeshPayloadToFile(*this, MeshGlobalTransform, PayloadFilepath);
|
|
}
|
|
}
|
|
|
|
#if WITH_ENGINE
|
|
bool FFbxParser::FetchMeshPayloadData(const FString& PayloadKey, const FTransform& MeshGlobalTransform, FMeshPayloadData& OutMeshPayloadData)
|
|
{
|
|
if (!PayloadContexts.Contains(PayloadKey))
|
|
{
|
|
UInterchangeResultError_Generic* Message = AddMessage<UInterchangeResultError_Generic>();
|
|
Message->Text = LOCTEXT("CannotRetrievePayload", "Cannot retrieve payload; payload key doesn't have any context.");
|
|
return false;
|
|
}
|
|
|
|
{
|
|
//Critical section to force payload to be fetch one by one with no concurrency.
|
|
FScopeLock Lock(&PayloadCriticalSection);
|
|
TSharedPtr<FPayloadContextBase>& PayloadContext = PayloadContexts.FindChecked(PayloadKey);
|
|
return PayloadContext->FetchMeshPayload(*this, MeshGlobalTransform, OutMeshPayloadData);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
bool FFbxParser::FetchAnimationBakeTransformPayload(const TArray<UE::Interchange::FAnimationPayloadQuery>& PayloadQueries, const FString& ResultFolder, FCriticalSection* ResultPayloadsCriticalSection, TAtomic<int64>& UniqueIdCounter, TMap<FString, FString>& ResultPayloads/*PayloadUniqueID to FilePath*/)
|
|
{
|
|
//Critical section to force payload to be fetch one by one with no concurrency.
|
|
FScopeLock Lock(&PayloadCriticalSection);
|
|
|
|
TMap<uint32, TArray<const UE::Interchange::FAnimationPayloadQuery*>> PayloadQueriesGrouped;
|
|
|
|
for (const UE::Interchange::FAnimationPayloadQuery& PayloadQuery : PayloadQueries)
|
|
{
|
|
TArray<const UE::Interchange::FAnimationPayloadQuery*>& PayloadQueriesForHash = PayloadQueriesGrouped.FindOrAdd(PayloadQuery.TimeDescription.GetHash());
|
|
PayloadQueriesForHash.Add(&PayloadQuery);
|
|
}
|
|
|
|
TArray<FText> OutErrorMessages;
|
|
|
|
bool bResult = true;
|
|
for (const TPair<uint32, TArray<const UE::Interchange::FAnimationPayloadQuery*>>& Group : PayloadQueriesGrouped)
|
|
{
|
|
bResult = FFbxAnimation::FetchAnimationBakeTransformPayload(*this, GetSDKScene(), PayloadContexts, Group.Value, ResultFolder, ResultPayloadsCriticalSection, UniqueIdCounter, ResultPayloads, OutErrorMessages) && bResult;
|
|
}
|
|
|
|
for (const FText& ErrorMessage : OutErrorMessages)
|
|
{
|
|
UInterchangeResultError_Generic* Message = AddMessage<UInterchangeResultError_Generic>();
|
|
Message->Text = ErrorMessage;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void ManageNamespace(const bool bKeepFbxNamespace, FString& ObjectName, FbxObject* Object)
|
|
{
|
|
if (bKeepFbxNamespace)
|
|
{
|
|
if (ObjectName.Contains(TEXT(":")))
|
|
{
|
|
ObjectName = ObjectName.Replace(TEXT(":"), TEXT("_"));
|
|
Object->SetName(TCHAR_TO_UTF8(*ObjectName));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove namespaces
|
|
int32 LastNamespaceTokenIndex = INDEX_NONE;
|
|
if (ObjectName.FindLastChar(TEXT(':'), LastNamespaceTokenIndex))
|
|
{
|
|
//+1 to remove the ':' character we found
|
|
ObjectName.RightChopInline(LastNamespaceTokenIndex + 1, EAllowShrinking::Yes);
|
|
Object->SetName(TCHAR_TO_UTF8(*ObjectName));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FFbxParser::EnsureNodeNameAreValid(const FString& BaseFilename)
|
|
{
|
|
TSet<FString> AllNodeName;
|
|
int32 CurrentNameIndex = 1;
|
|
for (int32 NodeIndex = 0; NodeIndex < SDKScene->GetNodeCount(); ++NodeIndex)
|
|
{
|
|
FbxNode* Node = SDKScene->GetNode(NodeIndex);
|
|
FString NodeName = UTF8_TO_TCHAR(Node->GetName());
|
|
if (NodeName.IsEmpty())
|
|
{
|
|
do
|
|
{
|
|
NodeName = TEXT("ncl1_") + FString::FromInt(CurrentNameIndex++);
|
|
} while (AllNodeName.Contains(NodeName));
|
|
|
|
Node->SetName(TCHAR_TO_UTF8(*NodeName));
|
|
if (!GIsAutomationTesting)
|
|
{
|
|
UInterchangeResultDisplay_Generic* Message = AddMessage<UInterchangeResultDisplay_Generic>();
|
|
Message->Text = FText::Format(LOCTEXT("EnsureNodeNameAreValid_NoNodeName", "Interchange FBX file Loading: Found node with no name, new node name is '{0}'"), FText::FromString(NodeName));
|
|
}
|
|
}
|
|
ManageNamespace(bKeepFbxNamespace, NodeName, Node);
|
|
|
|
// Do not allow node to be named same as filename as this creates problems later on (reimport)
|
|
if (AllNodeName.Contains(NodeName))
|
|
{
|
|
FString UniqueNodeName;
|
|
do
|
|
{
|
|
UniqueNodeName = NodeName + FString::FromInt(CurrentNameIndex++);
|
|
} while (AllNodeName.Contains(UniqueNodeName));
|
|
|
|
FbxString UniqueName(TCHAR_TO_UTF8(*UniqueNodeName));
|
|
Node->SetName(UniqueName);
|
|
|
|
if (!GIsAutomationTesting)
|
|
{
|
|
UInterchangeResultDisplay_Generic* Message = AddMessage<UInterchangeResultDisplay_Generic>();
|
|
Message->Text = FText::Format(LOCTEXT("EnsureNodeNameAreValid_NodeNameClash", "FBX File Loading: Found name clash, node '{0}' was renamed to '{1}'"), FText::FromString(NodeName), FText::FromString(UniqueNodeName));
|
|
}
|
|
}
|
|
AllNodeName.Add(NodeName);
|
|
}
|
|
}
|
|
|
|
void FFbxParser::CleanupFbxData()
|
|
{
|
|
auto MakeFbxObjectNameUnique = [bKeepFbxNamespaceClosure = bKeepFbxNamespace](FbxObject* Object, TMap<FString, int32>& Names)
|
|
{
|
|
FString ObjectName = UTF8_TO_TCHAR(Object->GetName());
|
|
ManageNamespace(bKeepFbxNamespaceClosure, ObjectName, Object);
|
|
if (int32* Count = Names.Find(ObjectName))
|
|
{
|
|
(*Count)++;
|
|
ObjectName += TEXT("_ncl_") + FString::FromInt(*Count);
|
|
Object->SetName(TCHAR_TO_UTF8(*ObjectName));
|
|
}
|
|
else
|
|
{
|
|
Names.Add(ObjectName, 0);
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Ensure Node Name Validity (uniqueness)
|
|
// Name clash must be global because unreal bones do not support name conflict (they are stored in an array, no hierarchy)
|
|
TMap<FString, int32> NodeNames;
|
|
for (int32 NodeIndex = 0; NodeIndex < SDKScene->GetNodeCount(); ++NodeIndex)
|
|
{
|
|
FbxNode* Node = SDKScene->GetNode(NodeIndex);
|
|
FString NodeName = UTF8_TO_TCHAR(Node->GetName());
|
|
if (NodeName.IsEmpty())
|
|
{
|
|
Node->SetName(TCHAR_TO_UTF8(TEXT("Node")));
|
|
}
|
|
MakeFbxObjectNameUnique(Node, NodeNames);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Ensure Mesh Name Validity (uniqueness)
|
|
// Name clash must be global because we will build Unique ID from the mesh name
|
|
TMap<FString, int32> MeshNames;
|
|
for (int32 GeometryIndex = 0; GeometryIndex < SDKScene->GetGeometryCount(); ++GeometryIndex)
|
|
{
|
|
FbxGeometry* Geometry = SDKScene->GetGeometry(GeometryIndex);
|
|
if (Geometry->GetAttributeType() != FbxNodeAttribute::eMesh)
|
|
{
|
|
continue;
|
|
}
|
|
FbxMesh* Mesh = static_cast<FbxMesh*>(Geometry);
|
|
if (!Mesh)
|
|
{
|
|
continue;
|
|
}
|
|
FString MeshName = UTF8_TO_TCHAR(Mesh->GetName());
|
|
if (MeshName.IsEmpty())
|
|
{
|
|
Mesh->SetName(TCHAR_TO_UTF8(TEXT("Mesh")));
|
|
}
|
|
MakeFbxObjectNameUnique(Mesh, MeshNames);
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////////////////////
|
|
// Ensure Material Name Validity (uniqueness)
|
|
// Name clash must be global because we will build Unique ID from the material name
|
|
TMap<FString, int32> MaterialNames;
|
|
for (int32 MaterialIndex = 0; MaterialIndex < SDKScene->GetMaterialCount(); ++MaterialIndex)
|
|
{
|
|
FbxSurfaceMaterial* Material = SDKScene->GetMaterial(MaterialIndex);
|
|
FString MaterialName = UTF8_TO_TCHAR(Material->GetName());
|
|
if (MaterialName.IsEmpty())
|
|
{
|
|
Material->SetName(TCHAR_TO_UTF8(TEXT("Material")));
|
|
}
|
|
MakeFbxObjectNameUnique(Material, MaterialNames);
|
|
}
|
|
}
|
|
|
|
void FFbxParser::ProcessExtraInformation(UInterchangeBaseNodeContainer& NodeContainer)
|
|
{
|
|
UInterchangeSourceNode* SourceNode = UInterchangeSourceNode::FindOrCreateUniqueInstance(&NodeContainer);
|
|
|
|
SourceNode->SetExtraInformation(TEXT("File Version"), FileDetails.FbxFileVersion);
|
|
SourceNode->SetExtraInformation(TEXT("File Creator"), FileDetails.FbxFileCreator);
|
|
SourceNode->SetExtraInformation(TEXT("File Units"), FileDetails.UnitSystem);
|
|
SourceNode->SetExtraInformation(TEXT("File Axis Direction"), FileDetails.AxisDirection);
|
|
SourceNode->SetExtraInformation(TEXT("File Frame Rate"), FileDetails.FrameRate);
|
|
|
|
// Analytics Data
|
|
{
|
|
using namespace UE::Interchange;
|
|
if (!FileDetails.ApplicationVendor.IsEmpty())
|
|
{
|
|
SourceNode->SetExtraInformation(FSourceNodeExtraInfoStaticData::GetApplicationVendorExtraInfoKey(), FileDetails.ApplicationVendor);
|
|
}
|
|
|
|
if (!FileDetails.ApplicationName.IsEmpty())
|
|
{
|
|
SourceNode->SetExtraInformation(FSourceNodeExtraInfoStaticData::GetApplicationNameExtraInfoKey(), FileDetails.ApplicationName);
|
|
}
|
|
|
|
if (!FileDetails.ApplicationVersion.IsEmpty())
|
|
{
|
|
SourceNode->SetExtraInformation(FSourceNodeExtraInfoStaticData::GetApplicationVersionExtraInfoKey(), FileDetails.ApplicationVersion);
|
|
}
|
|
}
|
|
}
|
|
} //ns Private
|
|
} //ns Interchange
|
|
} //ns UE
|
|
|
|
#undef LOCTEXT_NAMESPACE
|