Files
2025-05-18 13:04:45 +08:00

717 lines
22 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DataprepOperations.h"
#include "Components/StaticMeshComponent.h"
#include "Engine/Texture2D.h"
#include "Engine/StaticMesh.h"
#include "StaticMeshResources.h"
#include "Materials/MaterialInterface.h"
#include "Misc/FileHelper.h"
// UI related section
#include "DetailLayoutBuilder.h"
#include "DetailWidgetRow.h"
#include "Widgets/Input/STextComboBox.h"
#include "Widgets/Input/SEditableTextBox.h"
#define LOCTEXT_NAMESPACE "DatasmithMeshOperations"
#ifdef LOG_TIME
namespace DataprepOperationTime
{
typedef TFunction<void(FText)> FLogFunc;
class FTimeLogger
{
public:
FTimeLogger(const FString& InText, FLogFunc&& InLogFunc)
: StartTime( FPlatformTime::Cycles64() )
, Text( InText )
, LogFunc(MoveTemp(InLogFunc))
{
UE_LOG( LogDataprep, Log, TEXT("%s ..."), *Text );
}
~FTimeLogger()
{
// Log time spent to import incoming file in minutes and seconds
double ElapsedSeconds = FPlatformTime::ToSeconds64(FPlatformTime::Cycles64() - StartTime);
int ElapsedMin = int(ElapsedSeconds / 60.0);
ElapsedSeconds -= 60.0 * (double)ElapsedMin;
FText Msg = FText::Format( LOCTEXT("DataprepOperation_LogTime", "{0} took {1} min {2} s."), FText::FromString( Text ), ElapsedMin, FText::FromString( FString::Printf( TEXT("%.3f"), ElapsedSeconds ) ) );
LogFunc( Msg );
}
private:
uint64 StartTime;
FString Text;
FLogFunc LogFunc;
};
}
#endif
void UDataprepSetLODsOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
if(ReductionSettings.Num() > MAX_STATIC_MESH_LODS)
{
FText Message = FText::Format( LOCTEXT( "DatasmithMeshOperations_SetLODs_Max", "Limiting number of reduction settings to max allowed, {0}" ), MAX_STATIC_MESH_LODS );
LogWarning( Message );
}
// Limit size of array to MAX_STATIC_MESH_LODS
const int32 LODCount = FMath::Min( ReductionSettings.Num(), MAX_STATIC_MESH_LODS );
if( LODCount == 0 )
{
FText OutReason = FText( LOCTEXT( "DatasmithMeshOperations_SetLODs", "No reduction settings. Aborting operation..." ) );
LogInfo( OutReason );
return;
}
// Fill up mesh reduction struct
FStaticMeshReductionOptions ReductionOptions;
ReductionOptions.bAutoComputeLODScreenSize = bAutoComputeLODScreenSize;
ReductionOptions.ReductionSettings.SetNum( LODCount );
for(int32 Index = 0; Index < LODCount; ++Index)
{
ReductionOptions.ReductionSettings[Index].PercentTriangles = FMath::Clamp( ReductionSettings[Index].PercentTriangles, 0.f, 1.f );
ReductionOptions.ReductionSettings[Index].ScreenSize = FMath::Clamp( ReductionSettings[Index].ScreenSize, 0.f, 1.f );
}
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SetLods"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
TArray<UObject*> ModifiedStaticMeshes;
UDataprepOperationsLibrary::SetLods( InContext.Objects, ReductionOptions, ModifiedStaticMeshes );
if(ModifiedStaticMeshes.Num() > 0)
{
AssetsModified( MoveTemp( ModifiedStaticMeshes ) );
}
}
UDataprepSetLODGroupOperation::UDataprepSetLODGroupOperation()
{
TArray<FName> LODGroupNames;
UStaticMesh::GetLODGroups( LODGroupNames );
GroupName = LODGroupNames[0];
}
void UDataprepSetLODGroupOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SetLODGroup"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
TArray<UObject*> ModifiedStaticMeshes;
UDataprepOperationsLibrary::SetLODGroup( InContext.Objects, GroupName, ModifiedStaticMeshes );
if(ModifiedStaticMeshes.Num() > 0)
{
AssetsModified( MoveTemp( ModifiedStaticMeshes ) );
}
}
void UDataprepSetSimpleCollisionOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SetSimpleCollision"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
TArray<UObject*> ModifiedStaticMeshes;
UDataprepOperationsLibrary::SetSimpleCollision( InContext.Objects, ShapeType, ModifiedStaticMeshes );
if(ModifiedStaticMeshes.Num() > 0)
{
AssetsModified( MoveTemp( ModifiedStaticMeshes ) );
}
}
void UDataprepSetConvexDecompositionCollisionOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SetConvexDecompositionCollision"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
TArray<UObject*> ModifiedStaticMeshes;
UDataprepOperationsLibrary::SetConvexDecompositionCollision( InContext.Objects, HullCount, MaxHullVerts, HullPrecision, ModifiedStaticMeshes );
if(ModifiedStaticMeshes.Num() > 0)
{
AssetsModified( MoveTemp( ModifiedStaticMeshes ) );
}
}
void UDataprepSetMobilityOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SetMobility"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
UDataprepOperationsLibrary::SetMobility( InContext.Objects, MobilityType );
}
void UDataprepSetMaterialOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
if(Material == nullptr)
{
FText OutReason = FText( LOCTEXT( "DatasmithMeshOperations_SetMaterial", "No material specified. Aborting operation..." ) );
LogInfo( OutReason );
return;
}
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SetMaterial"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
UDataprepOperationsLibrary::SetMaterial( InContext.Objects, Material );
}
void UDataprepSubstituteMaterialOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
if(MaterialSubstitute == nullptr)
{
FText OutReason = FText( LOCTEXT( "DatasmithDirProducer_SubstituteMaterial", "No material specified. Aborting operation..." ) );
LogInfo( OutReason );
return;
}
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SubstituteMaterial"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
UDataprepOperationsLibrary::SubstituteMaterial( InContext.Objects, MaterialSearch, StringMatch, MaterialSubstitute );
}
void UDataprepSubstituteMaterialByTableOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
if(MaterialDataTable == nullptr)
{
FText OutReason = FText( LOCTEXT( "DatasmithDirProducer_SubstituteMaterialByTable", "No data table specified. Aborting operation..." ) );
LogInfo( OutReason );
return;
}
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SubstituteMaterialsByTable"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
UDataprepOperationsLibrary::SubstituteMaterialsByTable( InContext.Objects, MaterialDataTable );
}
void FDataprepSetLODGroupDetails::OnLODGroupChanged(TSharedPtr<FString> NewValue, ESelectInfo::Type /*SelectInfo*/)
{
int32 Index = LODGroupOptions.Find(NewValue);
if (Index != INDEX_NONE && LodGroupPropertyHandle.IsValid() )
{
LodGroupPropertyHandle->SetValue( LODGroupNames[Index] );
}
}
TSharedRef< SWidget > FDataprepSetLODGroupDetails::CreateWidget()
{
// Build list of LODGroup names the user will choose from
LODGroupNames.Reset();
UStaticMesh::GetLODGroups( LODGroupNames );
LODGroupOptions.Reset();
TArray<FText> LODGroupDisplayNames;
UStaticMesh::GetLODGroupsDisplayNames( LODGroupDisplayNames );
for (int32 GroupIndex = 0; GroupIndex < LODGroupDisplayNames.Num(); ++GroupIndex)
{
LODGroupOptions.Add( MakeShareable( new FString( LODGroupDisplayNames[GroupIndex].ToString() ) ) );
}
// Set displayed value to what is used by the SetLODGroup operation
int32 SelectedIndex = LODGroupNames.Find( DataprepOperation->GroupName );
if(SelectedIndex == INDEX_NONE)
{
SelectedIndex = 0;
DataprepOperation->GroupName = LODGroupNames[SelectedIndex];
}
// Create widget
return SNew( STextComboBox )
.OptionsSource( &LODGroupOptions )
.InitiallySelectedItem(LODGroupOptions[SelectedIndex])
.OnSelectionChanged( this, &FDataprepSetLODGroupDetails::OnLODGroupChanged );
}
void FDataprepSetLODGroupDetails::CustomizeDetails(IDetailLayoutBuilder & DetailBuilder)
{
TArray< TWeakObjectPtr< UObject > > Objects;
DetailBuilder.GetObjectsBeingCustomized( Objects );
check( Objects.Num() > 0 );
DataprepOperation = Cast< UDataprepSetLODGroupOperation >(Objects[0].Get());
check( DataprepOperation );
TArray<FName> CategoryNames;
DetailBuilder.GetCategoryNames( CategoryNames );
FName CategoryName = CategoryNames.Num() > 0 ? CategoryNames[0] : FName( TEXT("SetLOGGroup_Internal") );
IDetailCategoryBuilder& ImportSettingsCategoryBuilder = DetailBuilder.EditCategory( CategoryName, FText::GetEmpty(), ECategoryPriority::Important );
LodGroupPropertyHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UDataprepSetLODGroupOperation, GroupName));
// Hide GroupName property as it is replaced with custom widget
DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UDataprepSetLODGroupOperation, GroupName));
FDetailWidgetRow& CustomAssetImportRow = ImportSettingsCategoryBuilder.AddCustomRow( FText::FromString( TEXT( "LODGroup" ) ) );
CustomAssetImportRow.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("DatasmithMeshOperationsLabel", "LODGroupName"))
.ToolTipText(LOCTEXT("DatasmithMeshOperationsTooltip", "List of predefined LODGroup"))
.Font( DetailBuilder.GetDetailFont() )
];
CustomAssetImportRow.ValueContent()
[
CreateWidget()
];
}
void UDataprepSetMeshOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
if(StaticMesh == nullptr)
{
FText OutReason = FText( LOCTEXT( "DatasmithMeshOperations_SetMesh", "No mesh specified. Aborting operation..." ) );
LogInfo( OutReason );
return;
}
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SetMesh"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
UDataprepOperationsLibrary::SetMesh( InContext.Objects, StaticMesh );
}
void UDataprepAddTagsOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger(TEXT("AddTags"), [&](FText Text) { this->LogInfo(Text); });
#endif
// Execute operation
UDataprepOperationsLibrary::AddTags(InContext.Objects, Tags);
}
void UDataprepSetMetadataOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger(TEXT("AddMetadata"), [&](FText Text) { this->LogInfo(Text); });
#endif
// Execute operation
UDataprepOperationsLibrary::AddMetadata(InContext.Objects, Metadata);
}
void UDataprepConsolidateObjectsOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger(TEXT("ConsolidateObjects"), [&](FText Text) { this->LogInfo(Text); });
#endif
// Execute operation
UDataprepOperationsLibrary::ConsolidateObjects(InContext.Objects);
}
void UDataprepRandomizeTransformOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger(TEXT("RandomizeTransform"), [&](FText Text) { this->LogInfo(Text); });
#endif
// Execute operation
UDataprepOperationsLibrary::RandomizeTransform(InContext.Objects, TransformType, ReferenceFrame, Min, Max);
}
void UDataprepRandomizeTransformOperation::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
const FName PropertyName = PropertyChangedEvent.MemberProperty->GetFName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(UDataprepRandomizeTransformOperation, Min))
{
Max.X = FMath::Max(Max.X, Min.X);
Max.Y = FMath::Max(Max.Y, Min.Y);
Max.Z = FMath::Max(Max.Z, Min.Z);
}
if (PropertyName == GET_MEMBER_NAME_CHECKED(UDataprepRandomizeTransformOperation, Max))
{
Min.X = FMath::Min(Max.X, Min.X);
Min.Y = FMath::Min(Max.Y, Min.Y);
Min.Z = FMath::Min(Max.Z, Min.Z);
}
}
void UDataprepFlipFacesOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger(TEXT("FlipFaces"), [&](FText Text) { this->LogInfo(Text); });
#endif
TSet<UStaticMesh*> StaticMeshes;
// Re-create static meshes
for (UObject* Object : InContext.Objects)
{
if (AActor* Actor = Cast< AActor >(Object))
{
TInlineComponentArray<UStaticMeshComponent*> StaticMeshComponents(Actor);
for (UStaticMeshComponent* StaticMeshComponent : StaticMeshComponents)
{
UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh();
if (nullptr == StaticMesh)
{
continue;
}
StaticMeshes.Add(StaticMesh);
}
}
}
// Execute operation
UDataprepOperationsLibrary::FlipFaces(StaticMeshes);
// Re-create meshes render data
UStaticMesh::BatchBuild(StaticMeshes.Array());
}
void UDataprepSetOutputFolder::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger(TEXT("RandomizeTransform"), [&](FText Text) { this->LogInfo(Text); });
#endif
UDataprepOperationsLibrary::SetSubOuputFolder(InContext.Objects, FolderName);
}
void FDataprepSetOutputFolderDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
TArray< TWeakObjectPtr< UObject > > Objects;
DetailBuilder.GetObjectsBeingCustomized( Objects );
check( Objects.Num() > 0 );
Operation = Cast< UDataprepSetOutputFolder >(Objects[0].Get());
check( Operation );
TArray<FName> CategoryNames;
DetailBuilder.GetCategoryNames( CategoryNames );
IDetailCategoryBuilder& CategoryBuilder = DetailBuilder.EditCategory( NAME_None, FText::GetEmpty(), ECategoryPriority::Important );
FolderNamePropertyHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UDataprepSetOutputFolder, FolderName));
FolderNamePropertyHandle->MarkHiddenByCustomization();
FDetailWidgetRow& CustomAssetImportRow = CategoryBuilder.AddCustomRow( FText::FromString( TEXT( "Folder Name" ) ) );
CustomAssetImportRow.NameContent()
[
FolderNamePropertyHandle->CreatePropertyNameWidget()
];
CustomAssetImportRow.ValueContent()
[
SAssignNew(TextBox, SEditableTextBox)
.OnTextChanged(this, &FDataprepSetOutputFolderDetails::FolderName_TextChanged)
.OnTextCommitted(this, &FDataprepSetOutputFolderDetails::FolderName_TextCommited)
.Text(FText::FromString(Operation->FolderName))
];
}
void FDataprepSetOutputFolderDetails::FolderName_TextCommited(const FText& InText, ETextCommit::Type InCommitType)
{
if (bValidFolderName)
{
FolderNamePropertyHandle->SetValue(InText.ToString());
}
else
{
// New name is not valid: revert to old folder name
TextBox->SetText(FText::FromString(Operation->FolderName));
}
bValidFolderName = true;
}
void FDataprepSetOutputFolderDetails::FolderName_TextChanged(const FText& Text)
{
// Slash and Square brackets are invalid characters for a folder name
const FString InvalidChars = INVALID_LONGPACKAGE_CHARACTERS TEXT("/[]");
FText ErrorMessage;
FString FolderName = Text.ToString();
// See if the name contains invalid characters.
FString Char;
for( int32 CharIdx = 0; CharIdx < FolderName.Len(); ++CharIdx )
{
Char = FolderName.Mid(CharIdx, 1);
if ( InvalidChars.Contains(*Char) )
{
FString ReadableInvalidChars = InvalidChars;
ReadableInvalidChars.ReplaceInline(TEXT("\r"), TEXT(""));
ReadableInvalidChars.ReplaceInline(TEXT("\n"), TEXT(""));
ReadableInvalidChars.ReplaceInline(TEXT("\t"), TEXT(""));
ErrorMessage = FText::Format(LOCTEXT("InvalidFolderName_InvalidCharacters", "A folder name may not contain any of the following characters: {0}"), FText::FromString(ReadableInvalidChars));
break;
}
}
if (!ErrorMessage.IsEmpty() || !FFileHelper::IsFilenameValidForSaving(FolderName, ErrorMessage))
{
TextBox->SetError(ErrorMessage);
}
else
{
// Clear error
TextBox->SetError(FText::GetEmpty());
}
bValidFolderName = ErrorMessage.IsEmpty();
}
void UDataprepAddToLayerOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger(TEXT("AddToLayer"), [&](FText Text) { this->LogInfo(Text); });
#endif
// Execute operation
UDataprepOperationsLibrary::AddToLayer(InContext.Objects, LayerName);
}
void UDataprepSetCollisionComplexityOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger( TEXT("SetCollisionComplexity"), [&]( FText Text) { this->LogInfo( Text ); });
#endif
// Execute operation
TArray<UObject*> ModifiedStaticMeshes;
UDataprepOperationsLibrary::SetCollisionComplexity( InContext.Objects, CollisionTraceFlag, ModifiedStaticMeshes );
if(ModifiedStaticMeshes.Num() > 0)
{
AssetsModified( MoveTemp( ModifiedStaticMeshes ) );
}
}
void UDataprepSetMaxTextureSizeOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger(TEXT("SetMaxTextureSize"), [&](FText Text) { this->LogInfo(Text); });
#endif
TSet<UTexture2D*> Textures;
// Get the textures to resize
for (UObject* Object : InContext.Objects)
{
if (UTexture2D* Texture = Cast< UTexture2D >(Object))
{
const int32 TextureWidth = Texture->GetSizeX();
const int32 TextureHeight = Texture->GetSizeY();
const bool bPowerOfTwo = FMath::IsPowerOfTwo(TextureWidth) && FMath::IsPowerOfTwo(TextureHeight);
if (bPowerOfTwo || bAllowPadding)
{
Textures.Add(Texture);
}
}
}
// Execute operation
UDataprepOperationsLibrary::ResizeTextures(Textures.Array(), MaxTextureSize);
}
void UDataprepSetMaxTextureSizeOperation::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
const FName PropertyName = PropertyChangedEvent.MemberProperty->GetFName();
if (PropertyName == GET_MEMBER_NAME_CHECKED(UDataprepSetMaxTextureSizeOperation, MaxTextureSize))
{
if (!FMath::IsPowerOfTwo(MaxTextureSize))
{
MaxTextureSize = FMath::RoundUpToPowerOfTwo(MaxTextureSize);
}
}
}
void UDataprepSetNaniteSettingsOperation::OnExecution_Implementation(const FDataprepContext& InContext)
{
#ifdef LOG_TIME
DataprepOperationTime::FTimeLogger TimeLogger(TEXT("SetNaniteSettings"), [&](FText Text) { this->LogInfo(Text); });
#endif
// Execute operation
TArray<UObject*> ModifiedStaticMeshes;
UDataprepOperationsLibrary::SetNaniteSettings(InContext.Objects, bNaniteEnabled, PositionPrecision, PercentTriangles * 0.01f, ModifiedStaticMeshes);
if (ModifiedStaticMeshes.Num() > 0)
{
AssetsModified(MoveTemp(ModifiedStaticMeshes));
}
}
// Helper functions to replicate setting of PositionPrecision for Nanite settings
// in StaticMesh editor.
// Borrowed from FNaniteSettingsLayoutin StaticMeshEditorTool.cpp
namespace NaniteSettingsUtils
{
static const int32 DisplayPositionPrecisionMin = -6;
static const int32 DisplayPositionPrecisionMax = 13;
/** Strings representing valid PositionPrecision. */
static TArray< TSharedPtr< FString > > PositionPrecisionNames;
int32 PositionPrecisionIndexToValue(int32 Index)
{
check(Index >= 0);
if (Index == 0)
{
return MIN_int32;
}
else
{
int32 Value = DisplayPositionPrecisionMin + (Index - 1);
Value = FMath::Min(Value, DisplayPositionPrecisionMax);
return Value;
}
}
int32 PositionPrecisionValueToIndex(int32 Value)
{
if (Value == MIN_int32)
{
return 0;
}
else
{
Value = FMath::Clamp(Value, DisplayPositionPrecisionMin, DisplayPositionPrecisionMax);
return Value - DisplayPositionPrecisionMin + 1;
}
}
FString PositionPrecisionValueToDisplayString(int32 Value)
{
check(Value != MIN_int32);
if (Value <= 0)
{
return FString::Printf(TEXT("%dcm"), 1 << (-Value));
}
else
{
const float fValue = FMath::Exp2((double)-Value);
return FString::Printf(TEXT("1/%dcm (%.3gcm)"), 1 << Value, fValue);
}
}
}
void FDataprepSetNaniteSettingsDetails::OnPositionPrecisionChanged(TSharedPtr<FString> NewValue, ESelectInfo::Type /*SelectInfo*/)
{
using namespace NaniteSettingsUtils;
int32 Index = PositionPrecisionNames.Find(NewValue);
if (Index != INDEX_NONE && PositionPrecisionPropertyHandle.IsValid())
{
PositionPrecisionPropertyHandle->SetValue(PositionPrecisionIndexToValue(Index));
}
}
TSharedRef< SWidget > FDataprepSetNaniteSettingsDetails::CreateWidget()
{
using namespace NaniteSettingsUtils;
// Build list of PositionPrecision names the user will choose from
if (PositionPrecisionNames.Num() == 0)
{
PositionPrecisionNames.Reserve(20);
PositionPrecisionNames.Add(MakeShareable(new FString(TEXT("Auto"))));
for (int32 Value = DisplayPositionPrecisionMin; Value <= DisplayPositionPrecisionMax; ++Value)
{
PositionPrecisionNames.Add(MakeShareable(new FString(PositionPrecisionValueToDisplayString(Value))));
}
}
// Set displayed value to what is used in this operator's PositionPrecision
int32 SelectedIndex = PositionPrecisionValueToIndex(DataprepOperation->PositionPrecision);
// Create widget
return SNew(STextComboBox)
.OptionsSource(&PositionPrecisionNames)
.InitiallySelectedItem(PositionPrecisionNames[SelectedIndex])
.OnSelectionChanged(this, &FDataprepSetNaniteSettingsDetails::OnPositionPrecisionChanged);
}
void FDataprepSetNaniteSettingsDetails::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder)
{
TArray< TWeakObjectPtr< UObject > > Objects;
DetailBuilder.GetObjectsBeingCustomized(Objects);
check(Objects.Num() > 0);
DataprepOperation = Cast< UDataprepSetNaniteSettingsOperation >(Objects[0].Get());
check(DataprepOperation);
TArray<FName> CategoryNames;
DetailBuilder.GetCategoryNames(CategoryNames);
FName CategoryName = CategoryNames.Num() > 0 ? CategoryNames[0] : FName(TEXT("MeshOperation_Internal"));
IDetailCategoryBuilder& ImportSettingsCategoryBuilder = DetailBuilder.EditCategory(CategoryName, FText::GetEmpty(), ECategoryPriority::Important);
PositionPrecisionPropertyHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UDataprepSetNaniteSettingsOperation, PositionPrecision));
// Hide PositionPrecision property as it is replaced with custom widget
DetailBuilder.HideProperty(GET_MEMBER_NAME_CHECKED(UDataprepSetNaniteSettingsOperation, PositionPrecision));
FDetailWidgetRow& CustomAssetImportRow = ImportSettingsCategoryBuilder.AddCustomRow(FText::FromString(TEXT("Position Precision")));
CustomAssetImportRow.NameContent()
[
SNew(STextBlock)
.Text(LOCTEXT("DatasmithNaniteOperationsLabel", "PositionPrecision"))
.ToolTipText(LOCTEXT("DatasmithNaniteOperationsTooltip", "List of predefined position precision"))
.Font(DetailBuilder.GetDetailFont())
];
CustomAssetImportRow.ValueContent()
[
CreateWidget()
];
}
#undef LOCTEXT_NAMESPACE