Files
UnrealEngine/Engine/Plugins/Enterprise/VariantManager/Source/VariantManager/Private/VariantManagerPropertyCapturer.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

673 lines
28 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "VariantManagerPropertyCapturer.h"
#include "Components/MeshComponent.h"
#include "Components/LightComponent.h"
#include "CapturableProperty.h"
#include "EdGraphSchema_K2.h"
#include "VariantManagerUtils.h"
#include "SwitchActor.h"
#define LOCTEXT_NAMESPACE "VariantManagerPropertyCapturer"
class FPropertyCaptureHelper
{
public:
FPropertyCaptureHelper(const TArray<UObject*>& InObject, EPropertyValueCategory CategoriesToCapture, FString InTargetPropertyPath = FString(), bool bCaptureAllArrayIndices = false);
// Captures properties that fit set categories from the given objects (both set in constructor)
TArray<TSharedPtr<FCapturableProperty>>& CaptureProperties();
private:
void CaptureActorExceptionProperties(const AActor* Actor, FPropertyPath& PropertyPath, FString& PrettyPathString, TArray<FString>& ComponentNames);
void CaptureComponentExceptionProperties(const UActorComponent* Component, FPropertyPath& PropertyPath, FString& PrettyPathString, TArray<FString>& ComponentNames);
bool CanCaptureProperty(const UStruct* ContainerClass, const FProperty* Property, FName& OutSetterFunctionName);
void CaptureProp(FPropertyPath& PropertyPath, FString& PrettyPathString, TArray<FString>& ComponentNames, FName PropertySetterName = FName(), EPropertyValueCategory InCaptureType = EPropertyValueCategory::Generic);
void GetAllPropertyPathsRecursive(const void* ValuePtr, const UStruct* PropertySource, FPropertyPath PropertyPath, FString PrettyPathString, TArray<FString> ComponentNames);
private:
// If this is not None, we will only capture the property corresponding to this path, if it can
// be resolved. Use this to check if a random string is a valid property path for the objects
FString TargetPropertyPath;
TArray<FString> SegmentedTargetPropertyPath;
// If true, we won't capture the property of or step into components that are not the root component
bool bJustRootComponent;
// If true and if we have a TargetPropertyPath, a path like 'Tags[2]' will lead to the capturing of all
// array indices of 'Tags', so 'Tags[0]', 'Tags[1]', 'Tags[2]', etc. If false, only 'Tags[2]' will be captured.
bool bCaptureAllArrayIndices;
// Points to the parent instance object. We use this to determine if a component we stumble upon is
// on our object's component hierarchy or on some other object's
const UObject* CurrentOwnerObject;
EPropertyValueCategory CategoriesToCapture;
const TArray<UObject*>& TargetObjects;
TArray<TSharedPtr<FCapturableProperty>> CapturedPropertyPaths;
// Helper datastructures to prevent copying the same path twice. It is convenient to keep
// CapturedPropertyPaths as an array of TSharedPtrs so that it can be used directly in widgets, so
// we need these for quick comparisons. In addition, comparing via FString interacts well with our
// exceptions for transforms, materials and visibility
TSet<const UActorComponent*> CapturedComponents;
TSet<FString> CapturedPaths;
};
FPropertyCaptureHelper::FPropertyCaptureHelper(const TArray<UObject*>& InObjects, EPropertyValueCategory InCategoriesToCapture, FString InTargetPropertyPath, bool bInCaptureAllArrayIndices)
: TargetPropertyPath(InTargetPropertyPath)
, bCaptureAllArrayIndices(bInCaptureAllArrayIndices)
, CategoriesToCapture(InCategoriesToCapture)
, TargetObjects(InObjects)
{
// HACK: If we're aiming for something specific (e.g. visibility, transform, etc) we just care about the root component
bJustRootComponent = !EnumHasAnyFlags(InCategoriesToCapture, EPropertyValueCategory::Generic);
const TCHAR* Delimiter = PATH_DELIMITER;
SegmentedTargetPropertyPath.Empty();
TargetPropertyPath.ParseIntoArray(SegmentedTargetPropertyPath, &Delimiter, 1, true);
}
void FPropertyCaptureHelper::CaptureActorExceptionProperties(const AActor* Actor, FPropertyPath& PropertyPath, FString& PrettyPathString, TArray<FString>& ComponentNames)
{
if ( CapturedPaths.Contains( PrettyPathString ) )
{
return;
}
const ASwitchActor* SwitchActor = Cast<const ASwitchActor>( Actor );
if ( SwitchActor &&
(TargetPropertyPath.IsEmpty() || TargetPropertyPath == SWITCH_ACTOR_SELECTED_OPTION_NAME ) &&
EnumHasAnyFlags( CategoriesToCapture, EPropertyValueCategory::Option ) )
{
FString DisplayString = PrettyPathString + SWITCH_ACTOR_SELECTED_OPTION_NAME;
// Initialize the dialog row item to unchecked, like in CaptureProp
const bool bChecked = false;
// Capture directly here as we don't need to check the property path for this exception property (by calling CaptureProp), as we already know this exists for the switch actor
TSharedPtr<FCapturableProperty> NewCapture = MakeShared<FCapturableProperty>( DisplayString, PropertyPath, ComponentNames, bChecked, FName(), EPropertyValueCategory::Option );
CapturedPropertyPaths.Add( NewCapture );
CapturedPaths.Add( PrettyPathString );
}
}
void FPropertyCaptureHelper::CaptureComponentExceptionProperties(const UActorComponent* Component, FPropertyPath& PropertyPath, FString& PrettyPathString, TArray<FString>& ComponentNames)
{
if (const UMeshComponent* ComponentAsMeshComponent = Cast<const UMeshComponent>(Component))
{
if (EnumHasAnyFlags(CategoriesToCapture, EPropertyValueCategory::Material))
{
int32 NumMats = ComponentAsMeshComponent->GetNumMaterials();
const static FString MatString = FString(TEXT("Material["));
FArrayProperty* OverrideMatsProp = FVariantManagerUtils::GetOverrideMaterialsProperty();
PropertyPath.AddProperty(FPropertyInfo(OverrideMatsProp));
FPropertyInfo LeafInfo(OverrideMatsProp->Inner);
for (int32 Index = 0; Index < NumMats; Index++)
{
FString DisplayString = PrettyPathString + MatString + FString::FromInt(Index) + FString(TEXT("]"));
LeafInfo.ArrayIndex = Index;
PropertyPath.AddProperty(LeafInfo);
ComponentNames.Add(FString()); // One for the outer, other for the inner
ComponentNames.Add(FString());
CaptureProp(PropertyPath, DisplayString, ComponentNames, FName(), EPropertyValueCategory::Material);
PropertyPath = *PropertyPath.TrimPath(1);
ComponentNames.Pop();
ComponentNames.Pop();
}
PropertyPath = *PropertyPath.TrimPath(1);
}
}
if (const USceneComponent* ComponentAsSceneComponent = Cast<const USceneComponent>(Component))
{
FString DisplayString;
if (EnumHasAnyFlags(CategoriesToCapture, EPropertyValueCategory::RelativeLocation))
{
FStructProperty* RelativeLocationProp = FVariantManagerUtils::GetRelativeLocationProperty();
PropertyPath.AddProperty(FPropertyInfo(RelativeLocationProp));
ComponentNames.Add(FString());
DisplayString = PrettyPathString + FString(TEXT("Relative Location"));
CaptureProp(PropertyPath, DisplayString, ComponentNames, FName(TEXT("SetRelativeLocation")), EPropertyValueCategory::RelativeLocation);
PropertyPath = *PropertyPath.TrimPath(1);
ComponentNames.Pop();
}
if (EnumHasAnyFlags(CategoriesToCapture, EPropertyValueCategory::RelativeRotation))
{
FStructProperty* RelativeRotationProp = FVariantManagerUtils::GetRelativeRotationProperty();
PropertyPath.AddProperty(FPropertyInfo(RelativeRotationProp));
ComponentNames.Add(FString());
DisplayString = PrettyPathString + FString(TEXT("Relative Rotation"));
CaptureProp(PropertyPath, DisplayString, ComponentNames, FName(TEXT("SetRelativeRotation")), EPropertyValueCategory::RelativeRotation);
PropertyPath = *PropertyPath.TrimPath(1);
ComponentNames.Pop();
}
if (EnumHasAnyFlags(CategoriesToCapture, EPropertyValueCategory::RelativeScale3D))
{
FStructProperty* RelativeScale3DProp = FVariantManagerUtils::GetRelativeScale3DProperty();
PropertyPath.AddProperty(FPropertyInfo(RelativeScale3DProp));
ComponentNames.Add(FString());
// Important that it has this display name, as this will prevent us from capturing it twice, as there is a capturable RelativeScale3D property
DisplayString = PrettyPathString + FString(TEXT("Relative Scale 3D"));
CaptureProp(PropertyPath, DisplayString, ComponentNames, FName(TEXT("SetRelativeScale3D")), EPropertyValueCategory::RelativeScale3D);
PropertyPath = *PropertyPath.TrimPath(1);
ComponentNames.Pop();
}
if (EnumHasAnyFlags(CategoriesToCapture, EPropertyValueCategory::Visibility))
{
FBoolProperty* VisibilityProp = FVariantManagerUtils::GetVisibilityProperty();
PropertyPath.AddProperty(FPropertyInfo(VisibilityProp));
ComponentNames.Add(FString());
DisplayString = PrettyPathString + FString(TEXT("Visible"));
CaptureProp(PropertyPath, DisplayString, ComponentNames, FName(TEXT("SetVisibility")), EPropertyValueCategory::Visibility);
PropertyPath = *PropertyPath.TrimPath(1);
ComponentNames.Pop();
}
}
if (const ULightComponent* ComponentAsLightComponent = Cast<const ULightComponent>(Component))
{
FString DisplayString;
if (EnumHasAnyFlags(CategoriesToCapture, EPropertyValueCategory::Color))
{
FStructProperty* LightColorProp = FVariantManagerUtils::GetLightColorProperty();
PropertyPath.AddProperty(FPropertyInfo(LightColorProp));
ComponentNames.Add(FString());
DisplayString = PrettyPathString + FString(TEXT("Light Color"));
CaptureProp(PropertyPath, DisplayString, ComponentNames, FName(TEXT("SetLightColor")), EPropertyValueCategory::Color);
PropertyPath = *PropertyPath.TrimPath(1);
ComponentNames.Pop();
}
}
}
bool IsHiddenFunction(const UStruct* PropertyStructure, const FString& FunctionName)
{
static const FName HideFunctionsName(TEXT("HideFunctions"));
TArray<FString> HideFunctions;
if (const UClass* Class = Cast<const UClass>(PropertyStructure))
{
Class->GetHideFunctions(HideFunctions);
}
return HideFunctions.Contains(FunctionName);
}
bool FPropertyCaptureHelper::CanCaptureProperty(const UStruct* ContainerClass, const FProperty* Property, FName& OutSetterFunctionName)
{
if (EnumHasAnyFlags(CategoriesToCapture, EPropertyValueCategory::Generic))
{
// Writable in some way and not editor-only/deprecated
if (Property->HasAllPropertyFlags(CPF_Edit | CPF_BlueprintVisible) && !Property->HasAnyPropertyFlags(CPF_EditorOnly | CPF_Deprecated | CPF_DisableEditOnInstance))
{
// EditAnywhere and BlueprintReadWrite
if (!Property->HasAnyPropertyFlags(CPF_EditConst | CPF_BlueprintReadOnly))
{
OutSetterFunctionName = FName();
return true;
}
// See if it has a function setter instead (e.g. Intensity property is BlueprintReadOnly, but the same component has a SetIntensity function that is BlueprintCallable)
else if (Property->HasAllPropertyFlags(CPF_BlueprintReadOnly))
{
// Reference: SequencerObjectChangeListener.cpp
FString PropertyVarName = Property->GetName();
// This property fits in all criteria but can only be set during construction scripts, so it's not suitable
if (PropertyVarName == TEXT("bAutoActivate"))
{
OutSetterFunctionName = FName();
return false;
}
// If this is a bool property, strip off the 'b' so that the "Set" functions to be
// found are, for example, "SetHidden" instead of "SetbHidden"
if (Property->GetClass()->IsChildOf(FBoolProperty::StaticClass()))
{
PropertyVarName.RemoveFromStart("b", ESearchCase::CaseSensitive);
}
static const FString Set(TEXT("Set"));
const FString FunctionString = Set + PropertyVarName;
FName FunctionName = FName(*FunctionString);
UFunction* Function = nullptr;
if (const UClass* Class = Cast<const UClass>(ContainerClass))
{
Function = Class->FindFunctionByName(FunctionName);
}
static const FName DeprecatedFunctionName(TEXT("DeprecatedFunction"));
bool bFoundValidFunction =
Function &&
!Function->HasMetaData(DeprecatedFunctionName) &&
Function->HasAllFunctionFlags(FUNC_BlueprintCallable) &&
!IsHiddenFunction(ContainerClass, FunctionString);
// Check if the function has a parameter with the same type as the property
// And all other parameters have default arguments
if (bFoundValidFunction)
{
for( TFieldIterator<FProperty> It(Function); It && It->HasAnyPropertyFlags(CPF_Parm) && !It->HasAnyPropertyFlags(CPF_OutParm|CPF_ReturnParm); ++It)
{
FProperty* PropertyParam = *It;
checkSlow(PropertyParam); // Fix static analysis warning
bool bParamCanBeHandled = false;
if (PropertyParam->SameType(Property))
{
if (Property->GetClass()->IsChildOf(FStructProperty::StaticClass()))
{
if (CastField<FStructProperty>(Property)->Struct == CastField<FStructProperty>(PropertyParam)->Struct)
{
bParamCanBeHandled = true;
}
}
else
{
bParamCanBeHandled = true;
}
}
if(!bParamCanBeHandled)
{
FString Default;
UEdGraphSchema_K2::FindFunctionParameterDefaultValue(Function, PropertyParam, Default);
if (!Default.IsEmpty())
{
bParamCanBeHandled = true;
}
}
if (!bParamCanBeHandled)
{
//UE_LOG(LogVariantManager, Warning, TEXT("For property '%s', can't handle function setter '%s' param '%s'"), *Property->GetName(), *Function->GetName(), *PropertyParam->GetName());
bFoundValidFunction = false;
break;
}
}
}
if (bFoundValidFunction)
{
OutSetterFunctionName = FunctionName;
return true;
}
}
}
}
OutSetterFunctionName = FName();
return false;
}
void FPropertyCaptureHelper::CaptureProp(FPropertyPath& PropertyPath, FString& PrettyPathString, TArray<FString>& ComponentNames, FName PropertySetterName, EPropertyValueCategory InCaptureType)
{
if (CapturedPaths.Contains(PrettyPathString))
{
return;
}
// Slight hack here: We need the bCaptureAllArrayIndices trick because the events that we use to auto-expose
// property captures don't give granularity as to which array index it was that was modified, meaning we must
// capture them all. It is more annoying to determine the array information when constructing the path though, so we
// just pass along the base path (e.g. 'Tags') and bCaptureAllArrayIndices=true, and FPropertyCaptureHelper is
// in charge of capturing 'Tags[0]', 'Tags[1]', etc
bool bFitsTarget = false;
if (!TargetPropertyPath.IsEmpty() && PropertyPath.GetNumProperties() > 0)
{
FString StringToCompare;
if (bCaptureAllArrayIndices && PropertyPath.GetLeafMostProperty().ArrayIndex != INDEX_NONE)
{
int32 OpenBracketIndex;
PrettyPathString.FindLastChar(TEXT('['), OpenBracketIndex);
if (OpenBracketIndex != INDEX_NONE)
{
StringToCompare = PrettyPathString.Left(OpenBracketIndex);
}
}
else
{
StringToCompare = PrettyPathString;
}
bFitsTarget = (StringToCompare == TargetPropertyPath);
}
if (TargetPropertyPath.IsEmpty() || bFitsTarget)
{
TSharedPtr<FCapturableProperty> NewCapture = MakeShared<FCapturableProperty>(PrettyPathString, PropertyPath, ComponentNames, false, PropertySetterName, InCaptureType);
CapturedPropertyPaths.Add(NewCapture);
CapturedPaths.Add(PrettyPathString);
}
}
void FPropertyCaptureHelper::GetAllPropertyPathsRecursive(const void* ValuePtr, const UStruct* PropertySource, FPropertyPath PropertyPath, FString PrettyPathString, TArray<FString> ComponentNames)
{
FName PropertySetterName;
//@todo variantmanager clean this up, tons of duplication
for (TFieldIterator<FProperty> PropertyIterator(PropertySource); PropertyIterator; ++PropertyIterator)
{
FProperty* Property = *PropertyIterator;
if (Property)
{
FString PrettyString = Property->GetDisplayNameText().ToString();
// Add category if we're a inside a UStruct (completely arbitrary decision, but helps to handle
// the properties inside the structs we do support: FPostProcessSettings and the CineCamera structs)
if (const UScriptStruct* Struct = Cast<UScriptStruct>(PropertySource))
{
FString Category = Property->GetMetaData(TEXT("Category"));
if (!Category.IsEmpty())
{
Category = Category.Replace(TEXT("|"), PATH_DELIMITER);
int32 LastDelimiterIndex = Category.Find(PATH_DELIMITER, ESearchCase::CaseSensitive, ESearchDir::FromEnd);
FString LastCategorySegment = (LastDelimiterIndex == INDEX_NONE)? Category : Category.RightChop(LastDelimiterIndex+1);
// Some categories have the exact same name as the struct or the property, which is not helpful
// e.g. Component / Focus Settings / Focus Settings / Property
// or Component / Focus Method / Focus Method
if (!PrettyPathString.EndsWith(PATH_DELIMITER + Category + PATH_DELIMITER) &&
LastCategorySegment != PrettyString)
{
PrettyString = Category + PATH_DELIMITER + PrettyString;
}
}
}
// Update current path position
FPropertyInfo PropInfo = FPropertyInfo(Property);
PropertyPath.AddProperty(PropInfo);
PrettyPathString += PrettyString;
ComponentNames.Add(FString());
// Arrays of..
if (FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property))
{
FScriptArrayHelper ArrayHelper(ArrayProperty, ArrayProperty->ContainerPtrToValuePtr<void>(ValuePtr));
for (int32 Index = 0; Index < ArrayHelper.Num(); ++Index)
{
// Update current path position
PropertyPath.AddProperty(FPropertyInfo(ArrayProperty->Inner, Index));
FString InnerPrettyString = FString::Printf(TEXT("[%d]"), Index);
PrettyPathString += InnerPrettyString;
ComponentNames.Add(FString());
// ...structs
if (FStructProperty* StructProperty = CastField<FStructProperty>(ArrayProperty->Inner))
{
// Check the UArrayProperty's flags. The Inner's flags are unused for this
if (CanCaptureProperty(PropertySource, ArrayProperty, PropertySetterName) &&
FVariantManagerUtils::IsBuiltInStructProperty(ArrayProperty->Inner))
{
CaptureProp(PropertyPath, PrettyPathString, ComponentNames, PropertySetterName);
}
}
// ...objects
else if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(ArrayProperty->Inner))
{
void* ObjectContainer = ArrayHelper.GetRawPtr(Index);
UObject* Object = ObjectProperty->GetObjectPropertyValue(ObjectContainer);
if (UActorComponent* ObjAsComponent = Cast<UActorComponent>(Object))
{
// This prevents us from jumping between different actors and capturing the same component more than once through
// upward links (like AttachParent) or something else the user might have
// Transient components are created during play mode (like billboards, etc)
if (ObjAsComponent->GetOwner() == CurrentOwnerObject && !CapturedComponents.Contains(ObjAsComponent) && !ObjAsComponent->HasAnyFlags(RF_Transient))
{
FString NameString = ObjAsComponent->GetName();
ComponentNames[ComponentNames.Num()-1] = NameString;
// Replace inner array property name with component name
FString PrettyPathWithComponentName = PrettyPathString; // RootComponent/AttachChildren[0]
PrettyPathWithComponentName.RemoveFromEnd(InnerPrettyString); // RootComponent/AttachChildren
PrettyPathWithComponentName.RemoveFromEnd(PrettyString); // RootComponent/
PrettyPathWithComponentName += NameString; // RootComponent/Children[0] (Audio)
PrettyPathWithComponentName += PATH_DELIMITER; // RootComponent/Children[0] (Audio)/
CapturedComponents.Add(ObjAsComponent);
// Prevent us from capturing stuff from other components other than the root component if we're capturing
// a specific property (e.g. we don't care about the visibility of some hierarchy children or other component
// when capturing visibility specifically, we just care about the visibility of the root component)
if (!bJustRootComponent)
{
CaptureComponentExceptionProperties(ObjAsComponent, PropertyPath, PrettyPathWithComponentName, ComponentNames);
if (TargetPropertyPath.IsEmpty() || TargetPropertyPath.StartsWith(PrettyPathWithComponentName))
{
GetAllPropertyPathsRecursive(Object, Object->GetClass(), PropertyPath, PrettyPathWithComponentName, ComponentNames);
}
}
}
}
// Check the UArrayProperty's flags. The Inner's flags are unused for this
else if (CanCaptureProperty(PropertySource, ArrayProperty, PropertySetterName))
{
CaptureProp(PropertyPath, PrettyPathString, ComponentNames, PropertySetterName);
}
}
// ...simple properties
// Check the UArrayProperty's flags. The Inner's flags are unused for this
else if (CanCaptureProperty(PropertySource, ArrayProperty, PropertySetterName))
{
CaptureProp(PropertyPath, PrettyPathString, ComponentNames, PropertySetterName);
}
PropertyPath = *PropertyPath.TrimPath(1);
PrettyPathString.RemoveFromEnd(InnerPrettyString);
ComponentNames.Pop();
}
}
// Structs
else if (FStructProperty* StructProperty = CastField<FStructProperty>(Property))
{
bool bIsBuiltIn = FVariantManagerUtils::IsBuiltInStructProperty(Property);
if (CanCaptureProperty(PropertySource, Property, PropertySetterName) && bIsBuiltIn)
{
CaptureProp(PropertyPath, PrettyPathString, ComponentNames, PropertySetterName);
}
else if(!bIsBuiltIn)
{
UScriptStruct* Struct = StructProperty->Struct;
const void* StructAddressInValuePtr = StructProperty->ContainerPtrToValuePtr<const void>(ValuePtr);
if (FVariantManagerUtils::IsWalkableStructProperty(Property) &&
!Property->HasAnyPropertyFlags(CPF_EditorOnly | CPF_Deprecated | CPF_DisableEditOnInstance))
{
PrettyPathString += PATH_DELIMITER;
GetAllPropertyPathsRecursive(StructAddressInValuePtr, Struct, PropertyPath, PrettyPathString, ComponentNames);
PrettyPathString.RemoveFromEnd(PATH_DELIMITER);
}
}
}
// Objects
else if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property))
{
const void* ObjectContainer = ObjectProperty->ContainerPtrToValuePtr<const void>(ValuePtr);
const UObject* Object = ObjectProperty->GetObjectPropertyValue(ObjectContainer);
if (const UActorComponent* ObjAsComponent = Cast<const UActorComponent>(Object))
{
// We just step into the root component property
bool bIsRootComponentProperty = (ObjAsComponent->IsA(USceneComponent::StaticClass()) && Property->GetName() == TEXT("RootComponent"));
// Prevent us from capturing stuff from other components other than the root component if we're capturing
// a specific property (e.g. we don't care about the visibility of some hierarchy children or other component
// when capturing visibility specifically, we just care about the visibility of the root component)
bool bCanCaptureComponent = true;
const AActor* OwnerActor = Cast<const AActor>(CurrentOwnerObject);
if (OwnerActor->IsValidLowLevel() && bJustRootComponent && ObjAsComponent != OwnerActor->GetRootComponent())
{
bCanCaptureComponent = false;
}
// This prevents us from jumping between different actors and capturing the same component more than once through
// upward links (like AttachParent) or something else the user might have in a custom component
// Transient components are created during play mode (like billboards, etc)
if (ObjAsComponent->GetOwner() == CurrentOwnerObject &&
!CapturedComponents.Contains(ObjAsComponent) &&
!ObjAsComponent->HasAnyFlags(RF_Transient) &&
bIsRootComponentProperty &&
bCanCaptureComponent)
{
FString NameString = ObjAsComponent->GetName();
ComponentNames[ComponentNames.Num()-1] = NameString;
FString ComponentPrettyPath;
if (!ObjAsComponent->HasAnyFlags(RF_DefaultSubObject))
{
// If we don't have a DefaultSubObject tag, we're a generated component for a blueprint, which can be
// renamed by the user. So let's use that name instead
ComponentPrettyPath = NameString + PATH_DELIMITER;
}
else
{
// For DefaultSubObject components, the name can't be set by the user and is something like
// StaticMeshComponent0, so we keep the property name, which should be "Static Mesh Component".
ComponentPrettyPath = ObjAsComponent->GetClass()->GetDisplayNameText().ToString() + PATH_DELIMITER;
}
CapturedComponents.Add(ObjAsComponent);
CaptureComponentExceptionProperties(ObjAsComponent, PropertyPath, ComponentPrettyPath, ComponentNames);
if (TargetPropertyPath.IsEmpty() || TargetPropertyPath.StartsWith(ComponentPrettyPath))
{
GetAllPropertyPathsRecursive(Object, Object->GetClass(), PropertyPath, ComponentPrettyPath, ComponentNames);
}
}
}
else if (CanCaptureProperty(PropertySource, Property, PropertySetterName))
{
CaptureProp(PropertyPath, PrettyPathString, ComponentNames, PropertySetterName);
}
}
// Simple properties
else if (CanCaptureProperty(PropertySource, Property, PropertySetterName))
{
CaptureProp(PropertyPath, PrettyPathString, ComponentNames, PropertySetterName);
}
PropertyPath = *PropertyPath.TrimPath(1);
PrettyPathString.RemoveFromEnd(PrettyString);
ComponentNames.Pop();
}
}
}
TArray<TSharedPtr<FCapturableProperty>>& FPropertyCaptureHelper::CaptureProperties()
{
FPropertyPath InitialPropertyPath;
FString InitialPrettyString;
TArray<FString> InitialComponentNames;
for (UObject* TargetObject : TargetObjects)
{
ensure(TargetObject->IsValidLowLevel());
const void* TargetInstance = nullptr;
const UStruct* TargetClass = nullptr;
// If the passed in object is a class itself
if (UClass* AsClass = Cast<UClass>(TargetObject))
{
const UObject* CDO = AsClass->GetDefaultObject();
TargetClass = AsClass;
TargetInstance = CDO;
CurrentOwnerObject = CDO;
}
// If the passed in object is an instance of a class
else
{
TargetClass = TargetObject->GetClass();
TargetInstance = TargetObject;
CurrentOwnerObject = TargetObject;
}
CaptureActorExceptionProperties((AActor*)TargetInstance, InitialPropertyPath, InitialPrettyString, InitialComponentNames);
GetAllPropertyPathsRecursive(TargetInstance, TargetClass, InitialPropertyPath, InitialPrettyString, InitialComponentNames);
}
CapturedPropertyPaths.Sort([](const TSharedPtr<FCapturableProperty>& A, const TSharedPtr<FCapturableProperty>& B)
{
return A->DisplayName < B->DisplayName;
});
return CapturedPropertyPaths;
}
void FVariantManagerPropertyCapturer::CaptureProperties(const TArray<UObject*>& InObjectsToCapture, TArray<TSharedPtr<FCapturableProperty>>& OutCapturedProps, FString TargetPropertyPath, bool bCaptureAllArrayIndices)
{
EPropertyValueCategory All = (EPropertyValueCategory)~0;
FPropertyCaptureHelper Helper = FPropertyCaptureHelper(InObjectsToCapture, All, TargetPropertyPath, bCaptureAllArrayIndices);
OutCapturedProps = Helper.CaptureProperties();
}
void FVariantManagerPropertyCapturer::CaptureVisibility(const TArray<UObject*>& InObjectsToCapture, TArray<TSharedPtr<FCapturableProperty>>& OutCapturedProps)
{
FPropertyCaptureHelper Helper = FPropertyCaptureHelper(InObjectsToCapture, EPropertyValueCategory::Visibility);
OutCapturedProps = Helper.CaptureProperties();
}
void FVariantManagerPropertyCapturer::CaptureTransform(const TArray<UObject*>& InObjectsToCapture, TArray<TSharedPtr<FCapturableProperty>>& OutCapturedProps)
{
FPropertyCaptureHelper Helper = FPropertyCaptureHelper(InObjectsToCapture,
EPropertyValueCategory::RelativeLocation | EPropertyValueCategory::RelativeRotation | EPropertyValueCategory::RelativeScale3D);
OutCapturedProps = Helper.CaptureProperties();
}
void FVariantManagerPropertyCapturer::CaptureLocation(const TArray<UObject*>& InObjectsToCapture, TArray<TSharedPtr<FCapturableProperty>>& OutCapturedProps)
{
FPropertyCaptureHelper Helper = FPropertyCaptureHelper(InObjectsToCapture, EPropertyValueCategory::RelativeLocation);
OutCapturedProps = Helper.CaptureProperties();
}
void FVariantManagerPropertyCapturer::CaptureRotation(const TArray<UObject*>& InObjectsToCapture, TArray<TSharedPtr<FCapturableProperty>>& OutCapturedProps)
{
FPropertyCaptureHelper Helper = FPropertyCaptureHelper(InObjectsToCapture, EPropertyValueCategory::RelativeRotation);
OutCapturedProps = Helper.CaptureProperties();
}
void FVariantManagerPropertyCapturer::CaptureScale3D(const TArray<UObject*>& InObjectsToCapture, TArray<TSharedPtr<FCapturableProperty>>& OutCapturedProps)
{
FPropertyCaptureHelper Helper = FPropertyCaptureHelper(InObjectsToCapture, EPropertyValueCategory::RelativeScale3D);
OutCapturedProps = Helper.CaptureProperties();
}
void FVariantManagerPropertyCapturer::CaptureMaterial(const TArray<UObject*>& InObjectsToCapture, TArray<TSharedPtr<FCapturableProperty>>& OutCapturedProps)
{
FPropertyCaptureHelper Helper = FPropertyCaptureHelper(InObjectsToCapture, EPropertyValueCategory::Material);
OutCapturedProps = Helper.CaptureProperties();
}
#undef LOCTEXT_NAMESPACE