// Copyright Epic Games, Inc. All Rights Reserved. #include "DataprepOperationsLibrary.h" #include "Components/StaticMeshComponent.h" #include "DataprepOperationsLibraryUtil.h" #include "DataprepCoreUtils.h" #include "DataprepContentConsumer.h" #include "DatasmithAssetUserData.h" #include "DatasmithAreaLightActor.h" #include "Editor/EditorEngine.h" #include "Engine/StaticMesh.h" #include "Engine/Texture2D.h" #include "Materials/Material.h" #include "Materials/MaterialFunction.h" #include "Materials/MaterialFunctionInstance.h" #include "Materials/MaterialInstance.h" #include "ObjectTools.h" #include "PhysicsEngine/BodySetup.h" #include "StaticMeshEditorSubsystemHelpers.h" #include "StaticMeshOperations.h" #include "StaticMeshEditorSubsystem.h" #include "UObject/UObjectIterator.h" DEFINE_LOG_CATEGORY(LogDataprep); #define LOCTEXT_NAMESPACE "DataprepOperationsLibrary" extern UNREALED_API UEditorEngine* GEditor; void UDataprepOperationsLibrary::SetLods(const TArray& SelectedObjects, const FStaticMeshReductionOptions& ReductionOptions, TArray& ModifiedObjects) { TSet SelectedMeshes = DataprepOperationsLibraryUtil::GetSelectedMeshes(SelectedObjects); UStaticMeshEditorSubsystem* StaticMeshEditorSubsystem = GEditor->GetEditorSubsystem(); if (!StaticMeshEditorSubsystem) { return; } // Create LODs but do not commit changes for (UStaticMesh* StaticMesh : SelectedMeshes) { if (StaticMesh) { DataprepOperationsLibraryUtil::FScopedStaticMeshEdit StaticMeshEdit( StaticMesh ); StaticMeshEditorSubsystem->SetLodsWithNotification(StaticMesh, ReductionOptions, false); ModifiedObjects.Add( StaticMesh ); } } } void UDataprepOperationsLibrary::SetSimpleCollision(const TArray& SelectedObjects, const EScriptCollisionShapeType ShapeType, TArray& ModifiedObjects) { UStaticMeshEditorSubsystem* StaticMeshEditorSubsystem = GEditor->GetEditorSubsystem(); if (!StaticMeshEditorSubsystem) { return; } TSet SelectedMeshes = DataprepOperationsLibraryUtil::GetSelectedMeshes(SelectedObjects); // Make sure all static meshes to be processed have render data for NDOP types bool bNeedRenderData = false; switch (ShapeType) { case EScriptCollisionShapeType::NDOP10_X: case EScriptCollisionShapeType::NDOP10_Y: case EScriptCollisionShapeType::NDOP10_Z: case EScriptCollisionShapeType::NDOP18: case EScriptCollisionShapeType::NDOP26: { bNeedRenderData = true; break; } default: { break; } } DataprepOperationsLibraryUtil::FStaticMeshBuilder StaticMeshBuilder( bNeedRenderData ? SelectedMeshes : TSet() ); // Create LODs but do not commit changes for (UStaticMesh* StaticMesh : SelectedMeshes) { if (StaticMesh) { DataprepOperationsLibraryUtil::FScopedStaticMeshEdit StaticMeshEdit( StaticMesh ); // Remove existing simple collisions StaticMeshEditorSubsystem->RemoveCollisionsWithNotification( StaticMesh, false ); StaticMeshEditorSubsystem->AddSimpleCollisionsWithNotification( StaticMesh, ShapeType, false ); ModifiedObjects.Add( StaticMesh ); } } } void UDataprepOperationsLibrary::SetConvexDecompositionCollision(const TArray& SelectedObjects, int32 HullCount, int32 MaxHullVerts, int32 HullPrecision, TArray& ModifiedObjects) { TRACE_CPUPROFILER_EVENT_SCOPE(UDataprepOperationsLibrary::SetConvexDecompositionCollision) TSet SelectedMeshes = DataprepOperationsLibraryUtil::GetSelectedMeshes(SelectedObjects); // Make sure all static meshes to be processed have render data DataprepOperationsLibraryUtil::FStaticMeshBuilder StaticMeshBuilder(SelectedMeshes); TArray StaticMeshes = SelectedMeshes.Array(); StaticMeshes.RemoveAll([](UStaticMesh* StaticMesh) { return StaticMesh == nullptr; }); // Build complex collision UStaticMeshEditorSubsystem* StaticMeshEditorSubsystem = GEditor->GetEditorSubsystem();\ if (!StaticMeshEditorSubsystem) { return; } StaticMeshEditorSubsystem->BulkSetConvexDecompositionCollisionsWithNotification(StaticMeshes, HullCount, MaxHullVerts, HullPrecision, false); ModifiedObjects.Append(StaticMeshes); } void UDataprepOperationsLibrary::SubstituteMaterial(const TArray& SelectedObjects, const FString& MaterialSearch, EEditorScriptingStringMatchType StringMatch, UMaterialInterface* MaterialSubstitute) { TArray MaterialsUsed = DataprepOperationsLibraryUtil::GetUsedMaterials(SelectedObjects); SubstituteMaterial(SelectedObjects, MaterialSearch, StringMatch, MaterialsUsed, MaterialSubstitute); } void UDataprepOperationsLibrary::SubstituteMaterialsByTable(const TArray& SelectedObjects, const UDataTable* DataTable) { if (DataTable == nullptr || DataTable->GetRowStruct() == nullptr || !DataTable->GetRowStruct()->IsChildOf(FMaterialSubstitutionDataTable::StaticStruct())) { return; } TArray MaterialsUsed = DataprepOperationsLibraryUtil::GetUsedMaterials(SelectedObjects); const TMap& MaterialTableRowMap = DataTable->GetRowMap(); for (auto& MaterialTableRowEntry : MaterialTableRowMap) { const FMaterialSubstitutionDataTable* MaterialRow = (const FMaterialSubstitutionDataTable*)MaterialTableRowEntry.Value; if (MaterialRow != nullptr && MaterialRow->MaterialReplacement != nullptr) { SubstituteMaterial(SelectedObjects, MaterialRow->SearchString, MaterialRow->StringMatch, MaterialsUsed, MaterialRow->MaterialReplacement); } } } void UDataprepOperationsLibrary::SubstituteMaterial(const TArray& SelectedObjects, const FString& MaterialSearch, EEditorScriptingStringMatchType StringMatch, const TArray& MaterialList, UMaterialInterface* MaterialSubstitute) { TArray MatchingObjects = UEditorFilterLibrary::ByIDName(TArray(MaterialList), MaterialSearch, StringMatch, EEditorScriptingFilterType::Include); TArray MaterialsToReplace; for (UObject* Object : MatchingObjects) { if (UMaterialInterface* MaterialInterface = Cast(Object)) { MaterialsToReplace.Add(MaterialInterface); } } for (UMaterialInterface* MaterialToReplace : MaterialsToReplace) { for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { // Find the materials by iterating over every mesh component. TInlineComponentArray MeshComponents(Actor); for (UMeshComponent* MeshComponent : MeshComponents) { int32 MaterialCount = FMath::Max( MeshComponent->GetNumOverrideMaterials(), MeshComponent->GetNumMaterials() ); for (int32 Index = 0; Index < MaterialCount; ++Index) { if (MeshComponent->GetMaterial(Index) == MaterialToReplace) { MeshComponent->SetMaterial(Index, MaterialSubstitute); } } } } else if (UMeshComponent* MeshComponent = Cast< UMeshComponent >(Object)) { int32 MaterialCount = FMath::Max( MeshComponent->GetNumOverrideMaterials(), MeshComponent->GetNumMaterials() ); for (int32 Index = 0; Index < MaterialCount; ++Index) { if (MeshComponent->GetMaterial(Index) == MaterialToReplace) { MeshComponent->SetMaterial(Index, MaterialSubstitute); } } } else if (UStaticMesh* StaticMesh = Cast< UStaticMesh >(Object)) { DataprepOperationsLibraryUtil::FScopedStaticMeshEdit StaticMeshEdit( StaticMesh ); TArray& StaticMaterials = StaticMesh->GetStaticMaterials(); for (int32 Index = 0; Index < StaticMesh->GetStaticMaterials().Num(); ++Index) { if (StaticMesh->GetMaterial(Index) == MaterialToReplace) { DataprepOperationsLibraryUtil::SetMaterial( StaticMesh, Index, MaterialSubstitute ); } } } } } } void UDataprepOperationsLibrary::SetMobility( const TArray< UObject* >& SelectedObjects, EComponentMobility::Type MobilityType ) { for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { TInlineComponentArray SceneComponents(Actor); for (USceneComponent* SceneComponent : SceneComponents) { SceneComponent->SetMobility(MobilityType); } if (ADatasmithAreaLightActor* DatasmithAreaLightActor = Cast(Actor)) { DatasmithAreaLightActor->Mobility = MobilityType; } } else if (USceneComponent* SceneComponent = Cast< USceneComponent >(Object)) { SceneComponent->SetMobility(MobilityType); } } } void UDataprepOperationsLibrary::SetMaterial( const TArray< UObject* >& SelectedObjects, UMaterialInterface* MaterialSubstitute ) { for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { // Find the materials by iterating over every mesh component. TInlineComponentArray MeshComponents(Actor); for (UMeshComponent* MeshComponent : MeshComponents) { int32 MaterialCount = FMath::Max( MeshComponent->GetNumOverrideMaterials(), MeshComponent->GetNumMaterials() ); for (int32 Index = 0; Index < MaterialCount; ++Index) { MeshComponent->SetMaterial(Index, MaterialSubstitute); } } } else if (UStaticMesh* StaticMesh = Cast< UStaticMesh >(Object)) { DataprepOperationsLibraryUtil::FScopedStaticMeshEdit StaticMeshEdit( StaticMesh ); for (int32 Index = 0; Index < StaticMesh->GetStaticMaterials().Num(); ++Index) { DataprepOperationsLibraryUtil::SetMaterial( StaticMesh, Index, MaterialSubstitute ); } } else if (UMeshComponent* MeshComponent = Cast< UMeshComponent >(Object)) { int32 MaterialCount = FMath::Max( MeshComponent->GetNumOverrideMaterials(), MeshComponent->GetNumMaterials() ); for (int32 Index = 0; Index < MaterialCount; ++Index) { MeshComponent->SetMaterial(Index, MaterialSubstitute); } } } } void UDataprepOperationsLibrary::SetLODGroup( const TArray& SelectedObjects, FName& LODGroupName, TArray& ModifiedObjects ) { TArray LODGroupNames; UStaticMesh::GetLODGroups( LODGroupNames ); if ( LODGroupNames.Find( LODGroupName ) != INDEX_NONE ) { TSet SelectedMeshes = DataprepOperationsLibraryUtil::GetSelectedMeshes(SelectedObjects); // Apply the new LODGroup without rebuilding the static mesh for (UStaticMesh* StaticMesh : SelectedMeshes) { if(StaticMesh) { StaticMesh->SetLODGroup( LODGroupName, false); ModifiedObjects.Add( StaticMesh ); } } } } void UDataprepOperationsLibrary::SetMesh(const TArray& SelectedObjects, UStaticMesh* MeshSubstitute) { for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { // Find the meshes by iterating over every mesh component. TInlineComponentArray MeshComponents(Actor); for (UStaticMeshComponent* MeshComponent : MeshComponents) { if(MeshComponent) { MeshComponent->SetStaticMesh( MeshSubstitute ); } } } else if (UStaticMeshComponent* MeshComponent = Cast< UStaticMeshComponent >( Object )) { MeshComponent->SetStaticMesh( MeshSubstitute ); } } } void UDataprepOperationsLibrary::SubstituteMesh(const TArray& SelectedObjects, const FString& MeshSearch, EEditorScriptingStringMatchType StringMatch, UStaticMesh* MeshSubstitute) { TArray MeshesUsed = DataprepOperationsLibraryUtil::GetUsedMeshes(SelectedObjects); SubstituteMesh( SelectedObjects, MeshSearch, StringMatch, MeshesUsed, MeshSubstitute ); } void UDataprepOperationsLibrary::SubstituteMeshesByTable(const TArray& , const UDataTable* ) { } void UDataprepOperationsLibrary::SubstituteMesh(const TArray& SelectedObjects, const FString& MeshSearch, EEditorScriptingStringMatchType StringMatch, const TArray& MeshList, UStaticMesh* MeshSubstitute) { TArray MatchingObjects = UEditorFilterLibrary::ByIDName(TArray(MeshList), MeshSearch, StringMatch, EEditorScriptingFilterType::Include); TSet MeshesToReplace; for (UObject* Object : MatchingObjects) { if (UStaticMesh* StaticMesh = Cast(Object)) { MeshesToReplace.Add(StaticMesh); } } for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { // Find the meshes by iterating over every mesh component. TInlineComponentArray MeshComponents(Actor); for (UStaticMeshComponent* MeshComponent : MeshComponents) { if( MeshesToReplace.Contains( MeshComponent->GetStaticMesh() ) ) { MeshComponent->SetStaticMesh( MeshSubstitute ); } } } } } void UDataprepOperationsLibrary::AddTags(const TArray< UObject* >& SelectedObjects, const TArray& InTags) { TFunction&)> AddNewTags = [&InTags](TArray& InExistingTags) { for (int TagIndex = 0; TagIndex < InTags.Num(); ++TagIndex) { if (!InTags[TagIndex].IsNone() && (INDEX_NONE == InExistingTags.Find(InTags[TagIndex]))) { InExistingTags.Add(InTags[TagIndex]); } } }; for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { AddNewTags(Actor->Tags); } else if (UActorComponent* Comp = Cast< UActorComponent >(Object)) { AddNewTags(Comp->ComponentTags); } } } void UDataprepOperationsLibrary::AddMetadata(const TArray& SelectedObjects, const TMap& InMetadata) { UDatasmithAssetUserData::FMetaDataContainer Metadata; // Add Datasmith meta data int32 ValueCount = InMetadata.Num(); Metadata.Reserve(ValueCount); for (auto& Elem : InMetadata) { Metadata.Add(Elem.Key, *Elem.Value); } Metadata.KeySort(FNameLexicalLess()); if (Metadata.Num() > 0) { for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { UActorComponent* ActorComponent = Actor->GetRootComponent(); if (ActorComponent) { Object = ActorComponent; } } if (Object->GetClass()->ImplementsInterface(UInterface_AssetUserData::StaticClass())) { IInterface_AssetUserData* AssetUserData = Cast< IInterface_AssetUserData >(Object); UDatasmithAssetUserData* DatasmithUserData = AssetUserData->GetAssetUserData< UDatasmithAssetUserData >(); if (!DatasmithUserData) { DatasmithUserData = NewObject(Object, NAME_None, RF_Public | RF_Transactional); AssetUserData->AddAssetUserData(DatasmithUserData); } DatasmithUserData->MetaData.Append(Metadata); } } } } void UDataprepOperationsLibrary::ConsolidateObjects(const TArray< UObject* >& SelectedObjects) { if (SelectedObjects.Num() < 2) { return; } // Use the first object as the consolidation object. UObject* ObjectToConsolidateTo = SelectedObjects[0]; check(ObjectToConsolidateTo); UObject* Outer = ObjectToConsolidateTo->GetOuter(); if (nullptr == Outer || !Outer->IsA(UPackage::StaticClass())) { UE_LOG(LogDataprep, Warning, TEXT("Consolidate failed: the object %s is not an asset"), *ObjectToConsolidateTo->GetName()); return; } const UClass* ComparisonClass = ObjectToConsolidateTo->GetClass(); check(ComparisonClass); TArray OutCompatibleObjects; // Iterate over each proposed consolidation object, checking if each shares a common class with the consolidation objects, or at least, a common base that // is allowed as an exception (currently only exceptions made for textures and materials). for (int32 ObjectIndex = 1; ObjectIndex < SelectedObjects.Num(); ++ObjectIndex) { UObject* CurProposedObj = SelectedObjects[ObjectIndex]; check(CurProposedObj); // You may not consolidate object redirectors if (CurProposedObj->GetClass()->IsChildOf(UObjectRedirector::StaticClass())) { continue; } if (CurProposedObj->GetClass() != ComparisonClass) { const UClass* NearestCommonBase = CurProposedObj->FindNearestCommonBaseClass(ComparisonClass); // If the proposed object doesn't share a common class or a common base that is allowed as an exception, it is not a compatible object if (!(NearestCommonBase->IsChildOf(UTexture::StaticClass())) && !(NearestCommonBase->IsChildOf(UMaterialInterface::StaticClass()))) { continue; } } // If execution has gotten this far, the current proposed object is compatible OutCompatibleObjects.Add(CurProposedObj); } // Sort assets according to their dependency // Texture first, then MaterialFunction, then ... auto GetAssetClassRank = [&](const UClass* AssetClass) -> int8 { if (AssetClass->IsChildOf(UTexture::StaticClass())) { return 0; } else if (AssetClass->IsChildOf(UMaterialFunction::StaticClass())) { return 1; } else if (AssetClass->IsChildOf(UMaterialFunctionInstance::StaticClass())) { return 2; } else if (AssetClass->IsChildOf(UMaterial::StaticClass())) { return 3; } else if (AssetClass->IsChildOf(UMaterialInstance::StaticClass())) { return 4; } else if (AssetClass->IsChildOf(UStaticMesh::StaticClass())) { return 5; } return 6; }; Algo::Sort(OutCompatibleObjects, [&](const UObject* A, const UObject* B) { int8 AValue = A ? GetAssetClassRank(A->GetClass()) : 7; int8 BValue = B ? GetAssetClassRank(B->GetClass()) : 7; return AValue > BValue; }); // ObjectTools::ConsolidateObjects is creating undesired Redirectors // Collect existing redirectors to identify the newly created ones TSet ExistingRedirectors; for (TObjectIterator Itr; Itr; ++Itr) { ExistingRedirectors.Add(*Itr); } // Perform the object consolidation ObjectTools::ConsolidateObjects(ObjectToConsolidateTo, OutCompatibleObjects, false); // Delete UObjectRedirector objects created by ObjectTools::ConsolidateObjects TArray RedirectorsToDelete; for (TObjectIterator Itr; Itr; ++Itr) { if (!ExistingRedirectors.Contains(*Itr)) { FDataprepCoreUtils::MoveToTransientPackage(*Itr); RedirectorsToDelete.Add(*Itr); } } if (RedirectorsToDelete.Num() > 0) { FDataprepCoreUtils::PurgeObjects(RedirectorsToDelete); } } void UDataprepOperationsLibrary::RandomizeTransform(const TArray& SelectedObjects, ERandomizeTransformType TransformType, ERandomizeTransformReferenceFrame ReferenceFrame, const FVector& Min, const FVector& Max) { for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { if (!Actor->GetRootComponent()) { continue; } // Generate random offset for X/Y/Z and apply depending on selected transform component const FVector Offset(FMath::RandRange(Min.X, Max.X), FMath::RandRange(Min.Y, Max.Y), FMath::RandRange(Min.Z, Max.Z)); USceneComponent* RootComponent = Actor->GetRootComponent(); switch (TransformType) { case ERandomizeTransformType::Rotation: { const FRotator OffsetRotation = FRotator::MakeFromEuler(Offset); if (ReferenceFrame == ERandomizeTransformReferenceFrame::World) { RootComponent->SetWorldRotation(RootComponent->GetComponentRotation() + OffsetRotation); } else { RootComponent->SetRelativeRotation(RootComponent->GetRelativeRotation() + OffsetRotation); } break; } case ERandomizeTransformType::Scale: { if (ReferenceFrame == ERandomizeTransformReferenceFrame::World) { RootComponent->SetWorldScale3D(RootComponent->GetComponentScale() + Offset); } else { RootComponent->SetRelativeScale3D(RootComponent->GetRelativeScale3D() + Offset); } break; } case ERandomizeTransformType::Location: { if (ReferenceFrame == ERandomizeTransformReferenceFrame::World) { RootComponent->SetWorldLocation(RootComponent->GetComponentLocation() + Offset); } else { RootComponent->SetRelativeLocation(RootComponent->GetRelativeLocation() + Offset); } break; } } } } } void UDataprepOperationsLibrary::FlipFaces(const TSet< UStaticMesh* >& StaticMeshes) { for (UStaticMesh* StaticMesh : StaticMeshes) { if (nullptr == StaticMesh || !StaticMesh->IsMeshDescriptionValid(0)) { continue; } FMeshDescription* MeshDescription = StaticMesh->GetMeshDescription(0); UStaticMesh::FCommitMeshDescriptionParams Params; Params.bMarkPackageDirty = false; Params.bUseHashAsGuid = true; FStaticMeshOperations::FlipPolygons(*MeshDescription); StaticMesh->CommitMeshDescription(0, Params); } } void UDataprepOperationsLibrary::SetSubOuputLevel(const TArray& SelectedObjects, const FString& SubLevelName) { if(SubLevelName.IsEmpty()) { return; } for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { if (USceneComponent* RootComponent = Actor->GetRootComponent()) { if ( RootComponent->GetClass()->ImplementsInterface(UInterface_AssetUserData::StaticClass()) ) { if ( IInterface_AssetUserData* AssetUserDataInterface = Cast< IInterface_AssetUserData >( RootComponent ) ) { UDataprepConsumerUserData* DataprepContentUserData = AssetUserDataInterface->GetAssetUserData< UDataprepConsumerUserData >(); if ( !DataprepContentUserData ) { EObjectFlags Flags = RF_Public; DataprepContentUserData = NewObject< UDataprepConsumerUserData >( RootComponent, NAME_None, Flags ); AssetUserDataInterface->AddAssetUserData( DataprepContentUserData ); } DataprepContentUserData->AddMarker(UDataprepContentConsumer::RelativeOutput, SubLevelName); } } } } } } void UDataprepOperationsLibrary::SetSubOuputFolder(const TArray& SelectedObjects, const FString& SubFolderName) { if(SubFolderName.IsEmpty()) { return; } for (UObject* Object : SelectedObjects) { const bool bValidObject = Object->HasAnyFlags(RF_Public) && IsValid(Object) && Object->GetClass()->ImplementsInterface(UInterface_AssetUserData::StaticClass()); if (bValidObject) { if ( IInterface_AssetUserData* AssetUserDataInterface = Cast< IInterface_AssetUserData >( Object ) ) { UDataprepConsumerUserData* DataprepContentUserData = AssetUserDataInterface->GetAssetUserData< UDataprepConsumerUserData >(); if ( !DataprepContentUserData ) { EObjectFlags Flags = RF_Public; DataprepContentUserData = NewObject< UDataprepConsumerUserData >( Object, NAME_None, Flags ); AssetUserDataInterface->AddAssetUserData( DataprepContentUserData ); } DataprepContentUserData->AddMarker(UDataprepContentConsumer::RelativeOutput, SubFolderName); } } } } void UDataprepOperationsLibrary::AddToLayer(const TArray& SelectedObjects, const FName& LayerName) { if (LayerName == NAME_None) { return; } for (UObject* Object : SelectedObjects) { if (AActor* Actor = Cast< AActor >(Object)) { if (IsValid(Actor)) { Actor->Layers.AddUnique(LayerName); } } } } void UDataprepOperationsLibrary::SetCollisionComplexity(const TArray& InSelectedObjects, const ECollisionTraceFlag InCollisionTraceFlag, TArray& InModifiedObjects) { TSet SelectedMeshes = DataprepOperationsLibraryUtil::GetSelectedMeshes(InSelectedObjects); DataprepOperationsLibraryUtil::FStaticMeshBuilder StaticMeshBuilder( SelectedMeshes ); for (UStaticMesh* StaticMesh : SelectedMeshes) { if (StaticMesh) { DataprepOperationsLibraryUtil::FScopedStaticMeshEdit StaticMeshEdit( StaticMesh ); if (UBodySetup* BodySetup = StaticMesh->GetBodySetup()) { BodySetup->CollisionTraceFlag = InCollisionTraceFlag; InModifiedObjects.Add( StaticMesh ); } } } } void UDataprepOperationsLibrary::ResizeTextures(const TArray& InTextures, int32 InMaxSize) { static const FName MaxTextureSizeName = GET_MEMBER_NAME_CHECKED(UTexture, MaxTextureSize); FProperty* MaxTextureSizeProperty = FindFProperty( UTexture::StaticClass(), MaxTextureSizeName ); FPropertyChangedEvent PropertyChangedEvent(MaxTextureSizeProperty); for (UTexture2D* Texture : InTextures) { Texture->PreEditChange(MaxTextureSizeProperty); const int32 TextureWidth = Texture->GetSizeX(); const int32 TextureHeight = Texture->GetSizeY(); if (!FMath::IsPowerOfTwo(TextureWidth) || !FMath::IsPowerOfTwo(TextureHeight)) { // Need to specify power of two mode for non-pot textures Texture->PowerOfTwoMode = ETexturePowerOfTwoSetting::PadToPowerOfTwo; } Texture->MaxTextureSize = InMaxSize; Texture->PostEditChangeProperty(PropertyChangedEvent); } } void UDataprepOperationsLibrary::SetNaniteSettings(const TArray& SelectedObjects, bool bEnabled, int32 PositionPrecision, float PercentTriangles, TArray& ModifiedObjects) { #if WITH_EDITORONLY_DATA TSet SelectedMeshes = DataprepOperationsLibraryUtil::GetSelectedMeshes(SelectedObjects); FMeshNaniteSettings NewSettings; NewSettings.bEnabled = bEnabled; NewSettings.PositionPrecision = PositionPrecision; NewSettings.FallbackPercentTriangles = FMath::Clamp(PercentTriangles, 0.f, 1.f); // Apply Nanite settings but do not commit changes TArray ModifiedMeshes; ModifiedMeshes.Reserve(SelectedMeshes.Num()); for (UStaticMesh* StaticMesh : SelectedMeshes) { if (StaticMesh && StaticMesh->NaniteSettings != NewSettings) { StaticMesh->NaniteSettings = NewSettings; ModifiedMeshes.Add(StaticMesh); } } ModifiedObjects.Append(ModifiedMeshes); #endif // #if WITH_EDITORONLY_DATA } #undef LOCTEXT_NAMESPACE