Files
UnrealEngine/Engine/Plugins/Interchange/Runtime/Source/Pipelines/Private/InterchangeGenericMeshPipeline.cpp
2025-05-18 13:04:45 +08:00

595 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "InterchangeGenericMeshPipeline.h"
#include "Animation/AnimSequence.h"
#include "Animation/Skeleton.h"
#include "Engine/SkeletalMesh.h"
#include "Engine/StaticMesh.h"
#include "GeometryCache.h"
#include "InterchangeAnimationTrackSetNode.h"
#include "InterchangeGenericAssetsPipeline.h"
#include "InterchangeMeshNode.h"
#include "InterchangePipelineLog.h"
#include "InterchangePipelineMeshesUtilities.h"
#include "InterchangeSceneNode.h"
#include "InterchangeSkeletalMeshFactoryNode.h"
#include "InterchangeStaticMeshFactoryNode.h"
#include "InterchangeSourceData.h"
#include "Misc/Paths.h"
#include "Nodes/InterchangeBaseNode.h"
#include "Nodes/InterchangeBaseNodeContainer.h"
#include "PhysicsEngine/PhysicsAsset.h"
#include "InterchangeCustomVersion.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(InterchangeGenericMeshPipeline)
FString UInterchangeGenericMeshPipeline::GetPipelineCategory(UClass* AssetClass)
{
if (ensure(AssetClass))
{
if (AssetClass->IsChildOf(UStaticMesh::StaticClass()))
{
return TEXT("Static Meshes");
}
else if (AssetClass->IsChildOf(USkeletalMesh::StaticClass()))
{
return TEXT("Skeletal Meshes");
}
else if (AssetClass->IsChildOf(UGeometryCache::StaticClass()))
{
return TEXT("Geometry Caches");
}
}
return TEXT("Static Meshes");
}
void UInterchangeGenericMeshPipeline::AdjustSettingsForContext(const FInterchangePipelineContextParams& ContextParams)
{
Super::AdjustSettingsForContext(ContextParams);
#if WITH_EDITOR
check(CommonSkeletalMeshesAndAnimationsProperties.IsValid());
if (ContextParams.ContextType == EInterchangePipelineContext::None)
{
//We do not change the setting if we are in editing context
return;
}
bool bAutoDetectConvertStaticMeshToSkeletalMesh = false;
bool bContainStaticMesh = false;
bool bContainSkeletalMesh = false;
bool bContainGeometryCache = false;
bool bContainStaticMeshAnimationNode = false;
bool bIgnoreStaticMesh = false;
GetMeshesInformationFromTranslatedData(ContextParams.BaseNodeContainer, bAutoDetectConvertStaticMeshToSkeletalMesh, bContainStaticMesh, bContainSkeletalMesh, bContainGeometryCache, bContainStaticMeshAnimationNode, bIgnoreStaticMesh);
//Avoid creating physics asset when importing a LOD or the alternate skinning
if (ContextParams.ContextType == EInterchangePipelineContext::AssetCustomLODImport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetCustomLODReimport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetAlternateSkinningImport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetAlternateSkinningReimport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetCustomMorphTargetImport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetCustomMorphTargetReImport)
{
bCreatePhysicsAsset = false;
PhysicsAsset = nullptr;
LodGroup = NAME_None;
if (ContextParams.ContextType == EInterchangePipelineContext::AssetAlternateSkinningImport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetAlternateSkinningReimport)
{
CommonMeshesProperties->ForceAllMeshAsType = EInterchangeForceMeshType::IFMT_SkeletalMesh;
CommonMeshesProperties->bAutoDetectMeshType = false;
CommonMeshesProperties->bBakeMeshes = true;
CommonMeshesProperties->bBakePivotMeshes = false;
CommonMeshesProperties->bImportLods = false;
CommonMeshesProperties->bKeepSectionsSeparate = false;
CommonMeshesProperties->VertexColorImportOption = EInterchangeVertexColorImportOption::IVCIO_Ignore;
bImportSkeletalMeshes = true;
bImportStaticMeshes = false;
bBuildNanite = false;
bImportMorphTargets = false;
bImportVertexAttributes = false;
bUpdateSkeletonReferencePose = false;
SkeletalMeshImportContentType = EInterchangeSkeletalMeshContentType::All;
CommonSkeletalMeshesAndAnimationsProperties->Skeleton = nullptr;
CommonSkeletalMeshesAndAnimationsProperties->bImportOnlyAnimations = false;
}
else if (ContextParams.ContextType == EInterchangePipelineContext::AssetCustomMorphTargetImport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetCustomMorphTargetReImport)
{
//Custom morph target are imported has a combined static mesh
CommonMeshesProperties->ForceAllMeshAsType = EInterchangeForceMeshType::IFMT_StaticMesh;
CommonMeshesProperties->bAutoDetectMeshType = false;
CommonMeshesProperties->bBakeMeshes = true;
CommonMeshesProperties->bBakePivotMeshes = false;
CommonMeshesProperties->bImportLods = true;
CommonMeshesProperties->bKeepSectionsSeparate = false;
CommonMeshesProperties->VertexColorImportOption = EInterchangeVertexColorImportOption::IVCIO_Ignore;
bImportSkeletalMeshes = false;
bImportStaticMeshes = true;
bCombineStaticMeshes = true;
bBuildNanite = false;
LodGroup = NAME_None;
bCollision = false;
Collision = EInterchangeMeshCollision::None;
bImportCollisionAccordingToMeshName = false;
bGenerateLightmapUVs = false;
bGenerateDistanceFieldAsIfTwoSided = false;
bSupportFaceRemap = false;
}
else if (ContextParams.ContextType == EInterchangePipelineContext::AssetCustomLODImport
|| ContextParams.ContextType == EInterchangePipelineContext::AssetCustomLODReimport)
{
//We are importing custom LODs
if (ContextParams.ImportObjectType)
{
//If we have a provided import object type we can make sure we import the correct type
if (ContextParams.ImportObjectType->IsChildOf<UStaticMesh>())
{
bImportStaticMeshes = true;
CommonMeshesProperties->ForceAllMeshAsType = EInterchangeForceMeshType::IFMT_StaticMesh;
CommonMeshesProperties->bAutoDetectMeshType = false;
bImportSkeletalMeshes = false;
bCombineStaticMeshes = true;
LodGroup = NAME_None;
bSupportFaceRemap = false;
bCollision = false;
Collision = EInterchangeMeshCollision::None;
bImportCollisionAccordingToMeshName = false;
bGenerateLightmapUVs = false;
bGenerateDistanceFieldAsIfTwoSided = false;
}
else if (ContextParams.ImportObjectType->IsChildOf<USkeletalMesh>())
{
bImportSkeletalMeshes = true;
CommonMeshesProperties->ForceAllMeshAsType = EInterchangeForceMeshType::IFMT_SkeletalMesh;
CommonMeshesProperties->bAutoDetectMeshType = false;
bCreatePhysicsAsset = false;
bImportStaticMeshes = false;
}
else
{
CommonMeshesProperties->bAutoDetectMeshType = true;
}
}
}
}
const FString CommonMeshesCategory = UInterchangeGenericCommonMeshesProperties::GetPipelineCategory(nullptr);
const FString StaticMeshesCategory = UInterchangeGenericMeshPipeline::GetPipelineCategory(UStaticMesh::StaticClass());
const FString SkeletalMeshesCategory = UInterchangeGenericMeshPipeline::GetPipelineCategory(USkeletalMesh::StaticClass());
const FString GeometryCachesCategory = UInterchangeGenericMeshPipeline::GetPipelineCategory(UGeometryCache::StaticClass());
const FString CommonSkeletalMeshesAndAnimationCategory = UInterchangeGenericCommonSkeletalMeshesAndAnimationsProperties::GetPipelineCategory(nullptr);
TArray<FString> HideCategories;
TArray<FString> HideSubCategories;
if (ContextParams.ContextType == EInterchangePipelineContext::AssetReimport)
{
CommonMeshesProperties->bAutoDetectMeshType = false;
HideSubCategories.Add(TEXT("Build"));
if (USkeletalMesh* SkeletalMesh = Cast<USkeletalMesh>(ContextParams.ReimportAsset))
{
//Set the skeleton to the current asset skeleton
CommonSkeletalMeshesAndAnimationsProperties->Skeleton = SkeletalMesh->GetSkeleton();
PhysicsAsset = SkeletalMesh->GetPhysicsAsset();
if (PhysicsAsset.IsValid())
{
bCreatePhysicsAsset = false;
}
bImportStaticMeshes = false;
HideCategories.Add(StaticMeshesCategory);
HideCategories.Add(GeometryCachesCategory);
if(!bContainSkeletalMesh
|| SkeletalMeshImportContentType == EInterchangeSkeletalMeshContentType::Geometry
|| CommonMeshesProperties->ForceAllMeshAsType == EInterchangeForceMeshType::IFMT_StaticMesh)
{
CommonMeshesProperties->ForceAllMeshAsType = EInterchangeForceMeshType::IFMT_SkeletalMesh;
}
}
else if (UStaticMesh* StaticMesh = Cast<UStaticMesh>(ContextParams.ReimportAsset))
{
HideCategories.Add(SkeletalMeshesCategory);
HideCategories.Add(GeometryCachesCategory);
HideCategories.Add(CommonSkeletalMeshesAndAnimationCategory);
bImportSkeletalMeshes = false;
if (!bContainStaticMesh
|| CommonMeshesProperties->ForceAllMeshAsType == EInterchangeForceMeshType::IFMT_SkeletalMesh)
{
CommonMeshesProperties->ForceAllMeshAsType = EInterchangeForceMeshType::IFMT_StaticMesh;
}
}
else if (UAnimSequence* AnimSequence = Cast<UAnimSequence>(ContextParams.ReimportAsset))
{
HideCategories.Add(StaticMeshesCategory);
HideCategories.Add(SkeletalMeshesCategory);
HideCategories.Add(GeometryCachesCategory);
HideCategories.Add(CommonMeshesCategory);
}
else if (UGeometryCache* GeometryCache = Cast<UGeometryCache>(ContextParams.ReimportAsset))
{
HideCategories.Add(StaticMeshesCategory);
HideCategories.Add(SkeletalMeshesCategory);
HideCategories.Add(CommonMeshesCategory);
HideCategories.Add(CommonSkeletalMeshesAndAnimationCategory);
}
else if (ContextParams.ReimportAsset)
{
HideCategories.Add(StaticMeshesCategory);
HideCategories.Add(SkeletalMeshesCategory);
HideCategories.Add(GeometryCachesCategory);
HideCategories.Add(CommonMeshesCategory);
HideCategories.Add(CommonSkeletalMeshesAndAnimationCategory);
}
}
if (UInterchangePipelineBase* OuterMostPipeline = GetMostPipelineOuter())
{
if (bContainGeometryCache)
{
HideProperty(OuterMostPipeline, CommonMeshesProperties.Get(), GET_MEMBER_NAME_CHECKED(UInterchangeGenericCommonMeshesProperties, ForceAllMeshAsType));
}
constexpr bool bDoTransientSubPipeline = true;
if (UInterchangeGenericAssetsPipeline* ParentPipeline = Cast<UInterchangeGenericAssetsPipeline>(OuterMostPipeline))
{
if (ParentPipeline->ReimportStrategy == EReimportStrategyFlags::ApplyNoProperties)
{
for (const FString& HideSubCategoryName : HideSubCategories)
{
HidePropertiesOfSubCategory(OuterMostPipeline, this, HideSubCategoryName, bDoTransientSubPipeline);
}
}
}
for (const FString& HideCategoryName : HideCategories)
{
HidePropertiesOfCategory(OuterMostPipeline, this, HideCategoryName, bDoTransientSubPipeline);
}
}
#endif //WITH_EDITOR
}
#if WITH_EDITOR
bool UInterchangeGenericMeshPipeline::IsPropertyChangeNeedRefresh(const FPropertyChangedEvent& PropertyChangedEvent) const
{
static const TSet<FName> NeedRefreshProperties =
{
GET_MEMBER_NAME_CHECKED(UInterchangeGenericMeshPipeline, SkeletalMeshImportContentType),
GET_MEMBER_NAME_CHECKED(UInterchangeGenericMeshPipeline, bImportStaticMeshes),
GET_MEMBER_NAME_CHECKED(UInterchangeGenericMeshPipeline, bImportSkeletalMeshes),
GET_MEMBER_NAME_CHECKED(UInterchangeGenericMeshPipeline, bCreatePhysicsAsset),
GET_MEMBER_NAME_CHECKED(UInterchangeGenericMeshPipeline, bCombineStaticMeshes)
};
if (NeedRefreshProperties.Contains(PropertyChangedEvent.GetPropertyName()))
{
return true;
}
return Super::IsPropertyChangeNeedRefresh(PropertyChangedEvent);
}
#endif //WITH_EDITOR
void UInterchangeGenericMeshPipeline::PreDialogCleanup(const FName PipelineStackName)
{
//Do not change the physics asset if this pipeline is a re-import or an override pipeline
if (!IsFromReimportOrOverride())
{
PhysicsAsset = nullptr;
}
}
#if WITH_EDITOR
bool UInterchangeGenericMeshPipeline::GetPropertyPossibleValues(const FName PropertyPath, TArray<FString>& PossibleValues)
{
FString PropertyPathString = PropertyPath.ToString();
int32 PropertyNameIndex = INDEX_NONE;
if (PropertyPathString.FindLastChar(':', PropertyNameIndex))
{
PropertyPathString = PropertyPathString.RightChop(PropertyNameIndex+1);
}
if (PropertyPathString.Equals(GET_MEMBER_NAME_STRING_CHECKED(UInterchangeGenericMeshPipeline, LodGroup)))
{
TArray<FName> LODGroupNames;
UStaticMesh::GetLODGroups(LODGroupNames);
for (int32 GroupIndex = 0; GroupIndex < LODGroupNames.Num(); ++GroupIndex)
{
PossibleValues.Add(LODGroupNames[GroupIndex].GetPlainNameString());
}
return true;
}
//If we did not find any property call the super implementation
return Super::GetPropertyPossibleValues(PropertyPath, PossibleValues);
}
void UInterchangeGenericMeshPipeline::GetSupportAssetClasses(TArray<UClass*>& PipelineSupportAssetClasses) const
{
PipelineSupportAssetClasses.Add(UStaticMesh::StaticClass());
PipelineSupportAssetClasses.Add(USkeletalMesh::StaticClass());
if (bCreatePhysicsAsset && !PhysicsAsset.IsValid())
{
PipelineSupportAssetClasses.Add(UPhysicsAsset::StaticClass());
}
}
#endif
void UInterchangeGenericMeshPipeline::GetMeshesInformationFromTranslatedData(const UInterchangeBaseNodeContainer* InBaseNodeContainer
, bool& bAutoDetectConvertStaticMeshToSkeletalMesh
, bool& bContainStaticMesh
, bool& bContainSkeletalMesh
, bool& bContainGeometryCache
, bool& bContainStaticMeshAnimationNode
, bool& bIgnoreStaticMeshes) const
{
//Its valid to call GetMeshesInformationFromTranslatedData with a null container
if (!InBaseNodeContainer)
{
return;
}
bAutoDetectConvertStaticMeshToSkeletalMesh = false;
bContainStaticMesh = false;
bContainSkeletalMesh = false;
bContainGeometryCache = false;
bContainStaticMeshAnimationNode = false;
bIgnoreStaticMeshes = false;
{
TArray<FString> StaticMeshNodeUids;
InBaseNodeContainer->IterateNodesOfType<UInterchangeMeshNode>([&bContainSkeletalMesh, &bContainGeometryCache, &StaticMeshNodeUids](const FString& NodeUid, UInterchangeMeshNode* MeshNode)
{
if (!MeshNode->IsMorphTarget())
{
if (Cast<UInterchangeGeometryCacheNode>(MeshNode))
{
bContainGeometryCache = true;
}
else
{
MeshNode->IsSkinnedMesh() ? bContainSkeletalMesh = true : StaticMeshNodeUids.Add(NodeUid);
}
}
});
bContainStaticMesh = !StaticMeshNodeUids.IsEmpty();
TMap<const UInterchangeSceneNode*, bool> CacheProcessSceneNodes;
InBaseNodeContainer->BreakableIterateNodesOfType<UInterchangeTransformAnimationTrackNode>([&InBaseNodeContainer, &bContainStaticMeshAnimationNode, &StaticMeshNodeUids, &CacheProcessSceneNodes](const FString& NodeUid, UInterchangeTransformAnimationTrackNode* AnimationNode)
{
FString SceneNodeUid;
if (AnimationNode->GetCustomActorDependencyUid(SceneNodeUid))
{
if (const UInterchangeSceneNode* SceneNode = Cast<UInterchangeSceneNode>(InBaseNodeContainer->GetNode(SceneNodeUid)))
{
if (IsImpactingAnyMeshesRecursive(SceneNode, InBaseNodeContainer, StaticMeshNodeUids, CacheProcessSceneNodes))
{
bContainStaticMeshAnimationNode = true;
}
}
}
return bContainStaticMeshAnimationNode;
});
}
if (CommonMeshesProperties->bAutoDetectMeshType && CommonMeshesProperties->ForceAllMeshAsType == EInterchangeForceMeshType::IFMT_None)
{
if (!bContainSkeletalMesh && bContainStaticMesh)
{
//Auto detect some static mesh transform animations, we need to force the skeletal mesh type and recompute
bAutoDetectConvertStaticMeshToSkeletalMesh = bContainStaticMeshAnimationNode;
}
else if (bContainSkeletalMesh)
{
bIgnoreStaticMeshes = true;
}
}
}
void UInterchangeGenericMeshPipeline::PostLoad()
{
Super::PostLoad();
if(!bImportCollision_DEPRECATED)
{
bCollision = bImportCollision_DEPRECATED;
}
}
UInterchangePipelineMeshesUtilities* UInterchangeGenericMeshPipeline::CreateMeshPipelineUtilities(UInterchangeBaseNodeContainer* InBaseNodeContainer
, const UInterchangeGenericMeshPipeline* Pipeline)
{
UInterchangePipelineMeshesUtilities* CreatedPipelineMeshesUtilities = UInterchangePipelineMeshesUtilities::CreateInterchangePipelineMeshesUtilities(InBaseNodeContainer);
bool bAutoDetectConvertStaticMeshToSkeletalMesh = false;
bool bContainStaticMesh = false;
bool bContainSkeletalMesh = false;
bool bContainGeometryCache = false;
bool bContainStaticMeshAnimationNode = false;
bool bIgnoreStaticMeshes = false;
Pipeline->GetMeshesInformationFromTranslatedData(InBaseNodeContainer, bAutoDetectConvertStaticMeshToSkeletalMesh, bContainStaticMesh, bContainSkeletalMesh, bContainGeometryCache, bContainStaticMeshAnimationNode, bIgnoreStaticMeshes);
//Set the context option to use when querying the pipeline mesh utilities
FInterchangePipelineMeshesUtilitiesContext DataContext;
//We convert to skeletal mesh, only if the translated data do not have skeletal mesh
//Rigid mesh import is a fallback when there is no skinned mesh
DataContext.bConvertStaticMeshToSkeletalMesh = !bContainSkeletalMesh && (bAutoDetectConvertStaticMeshToSkeletalMesh || (Pipeline->CommonMeshesProperties->ForceAllMeshAsType == EInterchangeForceMeshType::IFMT_SkeletalMesh));
//Force static mesh convert all mesh to static mesh
DataContext.bConvertSkeletalMeshToStaticMesh = (Pipeline->CommonMeshesProperties->ForceAllMeshAsType == EInterchangeForceMeshType::IFMT_StaticMesh);
DataContext.bConvertStaticsWithMorphTargetsToSkeletals = Pipeline->CommonSkeletalMeshesAndAnimationsProperties->bConvertStaticsWithMorphTargetsToSkeletals;
DataContext.bImportMeshesInBoneHierarchy = Pipeline->CommonSkeletalMeshesAndAnimationsProperties->bImportMeshesInBoneHierarchy;
DataContext.bQueryGeometryOnlyIfNoInstance = Pipeline->CommonMeshesProperties->bBakeMeshes || Pipeline->CommonMeshesProperties->bBakePivotMeshes;
DataContext.bIgnoreStaticMeshes = bIgnoreStaticMeshes;
CreatedPipelineMeshesUtilities->SetContext(DataContext);
return CreatedPipelineMeshesUtilities;
}
void UInterchangeGenericMeshPipeline::ExecutePipeline(UInterchangeBaseNodeContainer* InBaseNodeContainer, const TArray<UInterchangeSourceData*>& InSourceDatas, const FString& ContentBasePath)
{
if (!InBaseNodeContainer)
{
UE_LOG(LogInterchangePipeline, Warning, TEXT("UInterchangeGenericMeshPipeline: Cannot execute pre-import pipeline because InBaseNodeContrainer is null."));
return;
}
BaseNodeContainer = InBaseNodeContainer;
SourceDatas.Empty(InSourceDatas.Num());
for (const UInterchangeSourceData* SourceData : InSourceDatas)
{
SourceDatas.Add(SourceData);
}
PipelineMeshesUtilities = CreateMeshPipelineUtilities(BaseNodeContainer, this);
//Create skeletalmesh factory nodes
ExecutePreImportPipelineSkeletalMesh();
//Create staticmesh factory nodes
ExecutePreImportPipelineStaticMesh();
//Create geometry cache factory nodes
ExecutePreImportPipelineGeometryCache();
}
void UInterchangeGenericMeshPipeline::ExecutePostImportPipeline(const UInterchangeBaseNodeContainer* InBaseNodeContainer, const FString& FactoryNodeKey, UObject* CreatedAsset, bool bIsAReimport)
{
//We do not use the provided base container since ExecutePreImportPipeline cache it
//We just make sure the same one is pass in parameter
if (!InBaseNodeContainer || !ensure(BaseNodeContainer == InBaseNodeContainer) || !CreatedAsset)
{
return;
}
const UInterchangeFactoryBaseNode* FactoryNode = BaseNodeContainer->GetFactoryNode(FactoryNodeKey);
if (!FactoryNode)
{
return;
}
//Set the last content type import
LastSkeletalMeshImportContentType = SkeletalMeshImportContentType;
PostImportSkeletalMesh(CreatedAsset, FactoryNode);
//Finish the physics asset import, it need the skeletal mesh render data to create the physics collision geometry
PostImportPhysicsAssetImport(CreatedAsset, FactoryNode);
}
void UInterchangeGenericMeshPipeline::SetReimportSourceIndex(UClass* ReimportObjectClass, const int32 SourceFileIndex)
{
if (ReimportObjectClass == USkeletalMesh::StaticClass())
{
switch (SourceFileIndex)
{
case 0:
{
//Geo and skinning
SkeletalMeshImportContentType = EInterchangeSkeletalMeshContentType::All;
}
break;
case 1:
{
//Geo only
SkeletalMeshImportContentType = EInterchangeSkeletalMeshContentType::Geometry;
}
break;
case 2:
{
//Skinning only
SkeletalMeshImportContentType = EInterchangeSkeletalMeshContentType::SkinningWeights;
}
break;
default:
{
//In case SourceFileIndex == INDEX_NONE //No specified options, we use the last imported content type
SkeletalMeshImportContentType = LastSkeletalMeshImportContentType;
}
};
}
}
#if WITH_EDITOR
bool UInterchangeGenericMeshPipeline::DoClassesIncludeAllEditableStructProperties(const TArray<const UClass*>& Classes, const UStruct* Struct)
{
check(IsInGameThread());
bool bResult = true;
const FName CategoryKey("Category");
for (const FProperty* Property = Struct->PropertyLink; Property; Property = Property->PropertyLinkNext)
{
//skip (transient, deprecated, const) property
if (Property->HasAnyPropertyFlags(CPF_Transient | CPF_Deprecated | CPF_EditConst))
{
continue;
}
//skip property that is not editable
if (!Property->HasAnyPropertyFlags(CPF_Edit))
{
continue;
}
const FObjectProperty* SubObject = CastField<FObjectProperty>(Property);
if (SubObject)
{
continue;
}
else if (const FString* PropertyCategoryString = Property->FindMetaData(CategoryKey))
{
FName PropertyName = Property->GetFName();
bool bFindProperty = false;
for (const UClass* Class : Classes)
{
if (Class->FindPropertyByName(PropertyName) != nullptr)
{
bFindProperty = true;
break;
}
}
//Ensure to notify
if (!bFindProperty)
{
UE_LOG(LogInterchangePipeline, Log, TEXT("The Interchange mesh pipeline does not include build property %s."), *PropertyName.ToString());
bResult = false;
}
}
}
return bResult;
}
#endif
bool UInterchangeGenericMeshPipeline::IsImpactingAnyMeshesRecursive(const UInterchangeSceneNode* SceneNode
, const UInterchangeBaseNodeContainer* InBaseNodeContainer
, const TArray<FString>& StaticMeshNodeUids
, TMap<const UInterchangeSceneNode*, bool>& CacheProcessSceneNodes)
{
bool& bIsImpactingCache = CacheProcessSceneNodes.FindOrAdd(SceneNode, false);
if (bIsImpactingCache)
{
return bIsImpactingCache;
}
FString AssetUid;
if (SceneNode->GetCustomAssetInstanceUid(AssetUid))
{
if (StaticMeshNodeUids.Contains(AssetUid))
{
bIsImpactingCache = true;
return true;
}
}
TArray<FString> Children = InBaseNodeContainer->GetNodeChildrenUids(SceneNode->GetUniqueID());
for (const FString& ChildUid : Children)
{
if (const UInterchangeSceneNode* ChildSceneNode = Cast<UInterchangeSceneNode>(InBaseNodeContainer->GetNode(ChildUid)))
{
if (IsImpactingAnyMeshesRecursive(ChildSceneNode, InBaseNodeContainer, StaticMeshNodeUids, CacheProcessSceneNodes))
{
return true;
}
}
}
return false;
}