Files
Machinist_700km/Plugins/HoudiniEngine/Source/HoudiniEngineRuntime/Private/HoudiniGenericAttribute.cpp
T
Andron666 9c38e93fa4 part7
2022-12-05 20:31:35 +05:00

1546 lines
52 KiB
C++

/*
* Copyright (c) <2021> Side Effects Software Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* 2. The name of Side Effects Software may not be used to endorse or
* promote products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED BY SIDE EFFECTS SOFTWARE "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
* NO EVENT SHALL SIDE EFFECTS SOFTWARE BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "HoudiniGenericAttribute.h"
#include "HoudiniEngineRuntimePrivatePCH.h"
#include "HoudiniAssetActor.h"
#include "HoudiniAssetComponent.h"
#include "HoudiniParameter.h"
#include "Engine/StaticMesh.h"
#include "Components/ActorComponent.h"
#include "Components/PrimitiveComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Landscape.h"
#include "PhysicsEngine/BodySetup.h"
#include "EditorFramework/AssetImportData.h"
#include "AI/Navigation/NavCollisionBase.h"
double
FHoudiniGenericAttribute::GetDoubleValue(int32 index) const
{
if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64))
{
if (DoubleValues.IsValidIndex(index))
return DoubleValues[index];
}
else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64))
{
if (IntValues.IsValidIndex(index))
return (double)IntValues[index];
}
else if (AttributeType == EAttribStorageType::STRING)
{
if (StringValues.IsValidIndex(index))
return FCString::Atod(*StringValues[index]);
}
return 0.0f;
}
void
FHoudiniGenericAttribute::GetDoubleTuple(TArray<double>& TupleValues, int32 index) const
{
TupleValues.SetNumZeroed(AttributeTupleSize);
for (int32 n = 0; n < AttributeTupleSize; n++)
TupleValues[n] = GetDoubleValue(index * AttributeTupleSize + n);
}
int64
FHoudiniGenericAttribute::GetIntValue(int32 index) const
{
if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64))
{
if (IntValues.IsValidIndex(index))
return IntValues[index];
}
else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64))
{
if (DoubleValues.IsValidIndex(index))
return (int64)DoubleValues[index];
}
else if (AttributeType == EAttribStorageType::STRING)
{
if (StringValues.IsValidIndex(index))
return FCString::Atoi64(*StringValues[index]);
}
return 0;
}
void
FHoudiniGenericAttribute::GetIntTuple(TArray<int64>& TupleValues, int32 index) const
{
TupleValues.SetNumZeroed(AttributeTupleSize);
for (int32 n = 0; n < AttributeTupleSize; n++)
TupleValues[n] = GetIntValue(index * AttributeTupleSize + n);
}
FString
FHoudiniGenericAttribute::GetStringValue(int32 index) const
{
if (AttributeType == EAttribStorageType::STRING)
{
if (StringValues.IsValidIndex(index))
return StringValues[index];
}
else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64))
{
if (IntValues.IsValidIndex(index))
return FString::FromInt((int32)IntValues[index]);
}
else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64))
{
if (DoubleValues.IsValidIndex(index))
return FString::SanitizeFloat(DoubleValues[index]);
}
return FString();
}
void
FHoudiniGenericAttribute::GetStringTuple(TArray<FString>& TupleValues, int32 index) const
{
TupleValues.SetNumZeroed(AttributeTupleSize);
for (int32 n = 0; n < AttributeTupleSize; n++)
TupleValues[n] = GetStringValue(index * AttributeTupleSize + n);
}
bool
FHoudiniGenericAttribute::GetBoolValue(int32 index) const
{
if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64))
{
if (DoubleValues.IsValidIndex(index))
return DoubleValues[index] == 0.0 ? false : true;
}
else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64))
{
if (IntValues.IsValidIndex(index))
return IntValues[index] == 0 ? false : true;
}
else if (AttributeType == EAttribStorageType::STRING)
{
if (StringValues.IsValidIndex(index))
return StringValues[index].Equals(TEXT("true"), ESearchCase::IgnoreCase) ? true : false;
}
return false;
}
void
FHoudiniGenericAttribute::GetBoolTuple(TArray<bool>& TupleValues, int32 index) const
{
TupleValues.SetNumZeroed(AttributeTupleSize);
for (int32 n = 0; n < AttributeTupleSize; n++)
TupleValues[n] = GetBoolValue(index * AttributeTupleSize + n);
}
void*
FHoudiniGenericAttribute::GetData()
{
if (AttributeType == EAttribStorageType::STRING)
{
if (StringValues.Num() > 0)
return StringValues.GetData();
}
else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64))
{
if (IntValues.Num() > 0)
return IntValues.GetData();
}
else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64))
{
if (DoubleValues.Num() > 0)
return DoubleValues.GetData();
}
return nullptr;
}
void
FHoudiniGenericAttribute::SetDoubleValue(double InValue, int32 index)
{
if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64))
{
if (!DoubleValues.IsValidIndex(index))
DoubleValues.SetNum(index + 1);
DoubleValues[index] = InValue;
}
else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64))
{
if (!IntValues.IsValidIndex(index))
IntValues.SetNum(index + 1);
IntValues[index] = InValue;
}
else if (AttributeType == EAttribStorageType::STRING)
{
if (!StringValues.IsValidIndex(index))
StringValues.SetNum(index + 1);
StringValues[index] = FString::SanitizeFloat(InValue);
}
}
void
FHoudiniGenericAttribute::SetDoubleTuple(const TArray<double>& InTupleValues, int32 index)
{
if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1))
return;
for (int32 n = 0; n < AttributeTupleSize; n++)
SetDoubleValue(InTupleValues[n], index * AttributeTupleSize + n);
}
void
FHoudiniGenericAttribute::SetIntValue(int64 InValue, int32 index)
{
if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64))
{
if (!IntValues.IsValidIndex(index))
IntValues.SetNum(index + 1);
IntValues[index] = InValue;
}
else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64))
{
if (!DoubleValues.IsValidIndex(index))
DoubleValues.SetNum(index + 1);
DoubleValues[index] = InValue;
}
else if (AttributeType == EAttribStorageType::STRING)
{
if (!StringValues.IsValidIndex(index))
StringValues.SetNum(index + 1);
StringValues[index] = FString::Printf(TEXT("%lld"), InValue);
}
}
void
FHoudiniGenericAttribute::SetIntTuple(const TArray<int64>& InTupleValues, int32 index)
{
if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1))
return;
for (int32 n = 0; n < AttributeTupleSize; n++)
SetIntValue(InTupleValues[n], index * AttributeTupleSize + n);
}
void
FHoudiniGenericAttribute::SetStringValue(const FString& InValue, int32 index)
{
if (AttributeType == EAttribStorageType::STRING)
{
if (!StringValues.IsValidIndex(index))
StringValues.SetNum(index + 1);
StringValues[index] = InValue;
}
else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64))
{
if (!IntValues.IsValidIndex(index))
IntValues.SetNum(index + 1);
IntValues[index] = FCString::Atoi64(*InValue);
}
else if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64))
{
if (!DoubleValues.IsValidIndex(index))
DoubleValues.SetNum(index + 1);
DoubleValues[index] = FCString::Atod(*InValue);
}
}
void
FHoudiniGenericAttribute::SetStringTuple(const TArray<FString>& InTupleValues, int32 index)
{
if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1))
return;
for (int32 n = 0; n < AttributeTupleSize; n++)
SetStringValue(InTupleValues[n], index * AttributeTupleSize + n);
}
void
FHoudiniGenericAttribute::SetBoolValue(bool InValue, int32 index)
{
if ((AttributeType == EAttribStorageType::FLOAT) || (AttributeType == EAttribStorageType::FLOAT64))
{
if (!DoubleValues.IsValidIndex(index))
DoubleValues.SetNum(index + 1);
DoubleValues[index] = InValue ? 1.0 : 0.0;
}
else if ((AttributeType == EAttribStorageType::INT) || (AttributeType == EAttribStorageType::INT64))
{
if (!IntValues.IsValidIndex(index))
IntValues.SetNum(index + 1);
IntValues[index] = InValue ? 1 : 0;
}
else if (AttributeType == EAttribStorageType::STRING)
{
if (!StringValues.IsValidIndex(index))
StringValues.SetNum(index + 1);
StringValues[index] = InValue ? "true" : "false";
}
}
void
FHoudiniGenericAttribute::SetBoolTuple(const TArray<bool>& InTupleValues, int32 index)
{
if (!InTupleValues.IsValidIndex(AttributeTupleSize - 1))
return;
for (int32 n = 0; n < AttributeTupleSize; n++)
SetBoolValue(InTupleValues[n], index * AttributeTupleSize + n);
}
bool
FHoudiniGenericAttribute::UpdatePropertyAttributeOnObject(
UObject* InObject, const FHoudiniGenericAttribute& InPropertyAttribute, const int32& AtIndex)
{
if (!InObject || InObject->IsPendingKill())
return false;
// Get the Property name
FString PropertyName = InPropertyAttribute.AttributeName;
if (PropertyName.IsEmpty())
return false;
// Some Properties need to be handle and modified manually...
if (PropertyName.Equals("CollisionProfileName", ESearchCase::IgnoreCase))
{
UPrimitiveComponent* PC = Cast<UPrimitiveComponent>(InObject);
if (IsValid(PC))
{
FString StringValue = InPropertyAttribute.GetStringValue(AtIndex);
FName Value = FName(*StringValue);
PC->SetCollisionProfileName(Value);
// Patch the StaticMeshGenerationProperties on the HAC
UHoudiniAssetComponent* HAC = Cast<UHoudiniAssetComponent>(InObject);
if (IsValid(HAC))
{
HAC->StaticMeshGenerationProperties.DefaultBodyInstance.SetCollisionProfileName(Value);
}
return true;
}
return false;
}
if (PropertyName.Equals("CollisionEnabled", ESearchCase::IgnoreCase))
{
UPrimitiveComponent* PC = Cast<UPrimitiveComponent>(InObject);
if (PC && !PC->IsPendingKill())
{
FString StringValue = InPropertyAttribute.GetStringValue(AtIndex);
if (StringValue.Equals("NoCollision", ESearchCase::IgnoreCase))
{
PC->SetCollisionEnabled(ECollisionEnabled::NoCollision);
return true;
}
else if (StringValue.Equals("QueryOnly", ESearchCase::IgnoreCase))
{
PC->SetCollisionEnabled(ECollisionEnabled::QueryOnly);
return true;
}
else if (StringValue.Equals("PhysicsOnly", ESearchCase::IgnoreCase))
{
PC->SetCollisionEnabled(ECollisionEnabled::PhysicsOnly);
return true;
}
else if (StringValue.Equals("QueryAndPhysics", ESearchCase::IgnoreCase))
{
PC->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
return true;
}
return false;
}
}
// Specialize CastShadow to avoid paying the cost of finding property + calling Property change twice
if (PropertyName.Equals("CastShadow", ESearchCase::IgnoreCase))
{
UPrimitiveComponent* Component = Cast< UPrimitiveComponent >(InObject);
if (Component && !Component->IsPendingKill())
{
bool Value = InPropertyAttribute.GetBoolValue(AtIndex);
Component->SetCastShadow(Value);
return true;
}
return false;
}
// Handle Component Tags manually here
if (PropertyName.Contains("Tags"))
{
UActorComponent* AC = Cast< UActorComponent >(InObject);
if (AC && !AC->IsPendingKill())
{
FName NameAttr = FName(*InPropertyAttribute.GetStringValue(AtIndex));
if (!AC->ComponentTags.Contains(NameAttr))
AC->ComponentTags.Add(NameAttr);
/*
for (int nIdx = 0; nIdx < InPropertyAttribute.AttributeCount; nIdx++)
{
FName NameAttr = FName(*InPropertyAttribute.GetStringValue(nIdx));
if (!AC->ComponentTags.Contains(NameAttr))
AC->ComponentTags.Add(NameAttr);
}
*/
return true;
}
return false;
}
#if WITH_EDITOR
// Handle landscape edit layers toggling
if (PropertyName.Equals("EnableEditLayers", ESearchCase::IgnoreCase)
|| PropertyName.Equals("bCanHaveLayersContent", ESearchCase::IgnoreCase))
{
ALandscape* Landscape = Cast<ALandscape>(InObject);
if (IsValid(Landscape))
{
if(InPropertyAttribute.GetBoolValue(AtIndex) != Landscape->CanHaveLayersContent())
Landscape->ToggleCanHaveLayersContent();
return true;
}
return false;
}
#endif
if (InObject->IsA<AHoudiniAssetActor>())
{
AHoudiniAssetActor* InHActor = Cast<AHoudiniAssetActor>(InObject);
UHoudiniAssetComponent* InHAC = Cast<UHoudiniAssetComponent>(InHActor ? InHActor->GetHoudiniAssetComponent() : InObject);
if (IsValid(InHAC))
{
// Try to see if we can find a parameter/input that matches the generic property attribute
UHoudiniParameter* FoundParam = InHAC->FindParameterByName(PropertyName);
if (IsValid(FoundParam))
{
FoundParam->GetName(PropertyName);
}
}
}
// Try to find the corresponding UProperty
void* OutContainer = nullptr;
FProperty* FoundProperty = nullptr;
UObject* FoundPropertyObject = nullptr;
#if WITH_EDITOR
// Try to match to source model properties when possible
if (UStaticMesh* SM = Cast<UStaticMesh>(InObject))
{
if (SM && !SM->IsPendingKill() && SM->GetNumSourceModels() > AtIndex)
{
bool bFoundProperty = false;
TryToFindProperty(&SM->GetSourceModel(AtIndex), SM->GetSourceModel(AtIndex).StaticStruct(), PropertyName, FoundProperty, bFoundProperty, OutContainer);
if (bFoundProperty)
{
FoundPropertyObject = InObject;
}
}
}
#endif
if (!FoundProperty && !FindPropertyOnObject(InObject, PropertyName, FoundProperty, FoundPropertyObject, OutContainer))
return false;
// Modify the Property we found
if (!ModifyPropertyValueOnObject(FoundPropertyObject, InPropertyAttribute, FoundProperty, OutContainer, AtIndex))
return false;
return true;
}
bool
FHoudiniGenericAttribute::FindPropertyOnObject(
UObject* InObject,
const FString& InPropertyName,
FProperty*& OutFoundProperty,
UObject*& OutFoundPropertyObject,
void*& OutContainer)
{
#if WITH_EDITOR
if (!InObject || InObject->IsPendingKill())
return false;
if (InPropertyName.IsEmpty())
return false;
UClass* ObjectClass = InObject->GetClass();
if (!ObjectClass || ObjectClass->IsPendingKill())
return false;
// Set the result pointer to null
OutContainer = nullptr;
OutFoundProperty = nullptr;
OutFoundPropertyObject = InObject;
bool bPropertyHasBeenFound = false;
FHoudiniGenericAttribute::TryToFindProperty(
InObject,
ObjectClass,
InPropertyName,
OutFoundProperty,
bPropertyHasBeenFound,
OutContainer);
/*
// TODO: Parsing needs to be made recursively!
// Iterate manually on the properties, in order to handle StructProperties correctly
for (TFieldIterator<FProperty> PropIt(ObjectClass, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
FProperty* CurrentProperty = *PropIt;
if (!CurrentProperty)
continue;
FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT(""));
FString Name = CurrentProperty->GetName();
// If the property name contains the uprop attribute name, we have a candidate
if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName))
{
OutFoundProperty = CurrentProperty;
// If it's an equality, we dont need to keep searching
if ((Name == InPropertyName) || (DisplayName == InPropertyName))
{
bPropertyHasBeenFound = true;
break;
}
}
// StructProperty need to be a nested struct
//if (UStructProperty* StructProperty = Cast< UStructProperty >(CurrentProperty))
// bPropertyHasBeenFound = TryToFindInStructProperty(InObject, InPropertyName, StructProperty, OutFoundProperty, OutStructContainer);
//else if (UArrayProperty* ArrayProperty = Cast< UArrayProperty >(CurrentProperty))
// bPropertyHasBeenFound = TryToFindInArrayProperty(InObject, InPropertyName, ArrayProperty, OutFoundProperty, OutStructContainer);
// Handle StructProperty
FStructProperty* StructProperty = CastField<FStructProperty>(CurrentProperty);
if (StructProperty)
{
// Walk the structs' properties and try to find the one we're looking for
UScriptStruct* Struct = StructProperty->Struct;
if (!Struct || Struct->IsPendingKill())
continue;
for (TFieldIterator<FProperty> It(Struct); It; ++It)
{
FProperty* Property = *It;
if (!Property)
continue;
DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT(""));
Name = Property->GetName();
// If the property name contains the uprop attribute name, we have a candidate
if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName))
{
// We found the property in the struct property, we need to keep the ValuePtr in the object
// of the structProp in order to be able to access the property value afterwards...
OutFoundProperty = Property;
OutStructContainer = StructProperty->ContainerPtrToValuePtr< void >(InObject, 0);
// If it's an equality, we dont need to keep searching
if ((Name == InPropertyName) || (DisplayName == InPropertyName))
{
bPropertyHasBeenFound = true;
break;
}
}
}
}
if (bPropertyHasBeenFound)
break;
}
if (bPropertyHasBeenFound)
return true;
*/
// Try with FindField??
if (!OutFoundProperty)
OutFoundProperty = FindFProperty<FProperty>(ObjectClass, *InPropertyName);
// Try with FindPropertyByName ??
if (!OutFoundProperty)
OutFoundProperty = ObjectClass->FindPropertyByName(*InPropertyName);
// We found the Property we were looking for
if (OutFoundProperty)
return true;
// Handle common properties nested in classes
// Static Meshes
UStaticMesh* SM = Cast<UStaticMesh>(InObject);
if (SM && !SM->IsPendingKill())
{
if (SM->BodySetup && FindPropertyOnObject(
SM->BodySetup, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer))
{
return true;
}
if (SM->AssetImportData && FindPropertyOnObject(
SM->AssetImportData, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer))
{
return true;
}
if (SM->NavCollision && FindPropertyOnObject(
SM->NavCollision, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer))
{
return true;
}
}
// For Actors, parse their components
AActor* Actor = Cast<AActor>(InObject);
if (Actor && !Actor->IsPendingKill())
{
TArray<USceneComponent*> AllComponents;
Actor->GetComponents<USceneComponent>(AllComponents, true);
int32 CompIdx = 0;
for (USceneComponent * SceneComponent : AllComponents)
{
if (!SceneComponent || SceneComponent->IsPendingKill())
continue;
if (FindPropertyOnObject(
SceneComponent, InPropertyName, OutFoundProperty, OutFoundPropertyObject, OutContainer))
{
return true;
}
}
}
// We found the Property we were looking for
if (OutFoundProperty)
return true;
#endif
return false;
}
bool
FHoudiniGenericAttribute::TryToFindProperty(
void* InContainer,
UStruct* InStruct,
const FString& InPropertyName,
FProperty*& OutFoundProperty,
bool& bOutPropertyHasBeenFound,
void*& OutContainer)
{
#if WITH_EDITOR
if (!InContainer)
return false;
if (!InStruct || InStruct->IsPendingKill())
return false;
if (InPropertyName.IsEmpty())
return false;
// Iterate manually on the properties, in order to handle StructProperties correctly
for (TFieldIterator<FProperty> PropIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropIt; ++PropIt)
{
FProperty* CurrentProperty = *PropIt;
if (!CurrentProperty)
continue;
FString DisplayName = CurrentProperty->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT(""));
FString Name = CurrentProperty->GetName();
// If the property name contains the uprop attribute name, we have a candidate
if (Name.Contains(InPropertyName) || DisplayName.Contains(InPropertyName))
{
OutFoundProperty = CurrentProperty;
OutContainer = InContainer;
// If it's an equality, we dont need to keep searching anymore
if ((Name == InPropertyName) || (DisplayName == InPropertyName))
{
bOutPropertyHasBeenFound = true;
break;
}
}
// Do a recursive parsing for StructProperties
FStructProperty* StructProperty = CastField<FStructProperty>(CurrentProperty);
if (StructProperty)
{
// Walk the structs' properties and try to find the one we're looking for
UScriptStruct* Struct = StructProperty->Struct;
if (!Struct || Struct->IsPendingKill())
continue;
TryToFindProperty(
StructProperty->ContainerPtrToValuePtr<void>(InContainer, 0),
Struct,
InPropertyName,
OutFoundProperty,
bOutPropertyHasBeenFound,
OutContainer);
}
if (bOutPropertyHasBeenFound)
break;
}
if (bOutPropertyHasBeenFound)
return true;
// We found the Property we were looking for
if (OutFoundProperty)
return true;
#endif
return false;
}
bool
FHoudiniGenericAttribute::ModifyPropertyValueOnObject(
UObject* InObject,
FHoudiniGenericAttribute InGenericAttribute,
FProperty* FoundProperty,
void* InContainer,
const int32& InAtIndex)
{
if (!InObject || InObject->IsPendingKill() || !FoundProperty)
return false;
// Determine the container to use (either InContainer if specified, or InObject)
void* Container = InContainer ? InContainer : InObject;
// Property class name, used for logging
const FString PropertyClassName = FoundProperty->GetClass() ? FoundProperty->GetClass()->GetName() : TEXT("Unknown");
// Initialize using the found property
FProperty* InnerProperty = FoundProperty;
AActor* InOwner = Cast<AActor>(InObject->GetOuter());
bool bHasModifiedProperty = false;
auto OnPropertyChanged = [InObject, InOwner, &bHasModifiedProperty](FProperty* InProperty)
{
#if WITH_EDITOR
FPropertyChangedEvent Evt(InProperty);
InObject->PostEditChangeProperty(Evt);
if (InOwner)
{
// If we are setting properties on an Actor component, we want to notify the
// actor of the changes too since the property change might be handled in the actor's
// PostEditChange callbacks (one such an example occurs when changing the material for a decal actor).
InOwner->PostEditChangeProperty(Evt);
}
#endif
bHasModifiedProperty = true;
};
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(FoundProperty);
TSharedPtr<FScriptArrayHelper_InContainer> ArrayHelper;
if (ArrayProperty)
{
InnerProperty = ArrayProperty->Inner;
ArrayHelper = MakeShareable<FScriptArrayHelper_InContainer>(new FScriptArrayHelper_InContainer(ArrayProperty, Container));
}
// TODO: implement support for array attributes received from Houdini
// Get the "proper" AtIndex in the flat array by using the attribute tuple size
// TODO: fix the issue when changing array of tuple properties!
const int32 TupleSize = InGenericAttribute.AttributeTupleSize;
int32 AtIndex = InAtIndex * TupleSize;
FFieldClass* PropertyClass = InnerProperty->GetClass();
if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) ||
PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass()))
{
// Supported non-struct properties
// If the attribute from Houdini has a tuple size > 1, we support setting it on arrays on the unreal side
// For example: a 3 float from Houdini can be set as a TArray<float> in Unreal.
// If this is an ArrayProperty, ensure that it is at least large enough for our tuple
// TODO: should we just set this to exactly our tuple size?
if (ArrayHelper.IsValid())
ArrayHelper->ExpandForIndex(TupleSize - 1);
for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex)
{
void* ValuePtr = nullptr;
if (ArrayHelper.IsValid())
{
ValuePtr = ArrayHelper->GetRawPtr(TupleIndex);
}
else
{
// If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim
// on the property to determine if our TupleIndex is in range, if not, give up, we cannot set any more
// of our tuple indices on this property.
if (TupleIndex >= InnerProperty->ArrayDim)
break;
ValuePtr = InnerProperty->ContainerPtrToValuePtr<void*>(Container, TupleIndex);
}
if (ValuePtr)
{
// Handle each property type that we support
if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()))
{
// Numeric properties are supported as floats and ints, and can also be set from a received string
FNumericProperty* const Property = CastField<FNumericProperty>(InnerProperty);
if (InGenericAttribute.AttributeType == EAttribStorageType::STRING)
{
Property->SetNumericPropertyValueFromString(ValuePtr, *InGenericAttribute.GetStringValue(AtIndex + TupleIndex));
}
else if (Property->IsFloatingPoint())
{
Property->SetFloatingPointPropertyValue(ValuePtr, InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex));
}
else if (Property->IsInteger())
{
Property->SetIntPropertyValue(ValuePtr, InGenericAttribute.GetIntValue(AtIndex + TupleIndex));
}
else
{
HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName);
return false;
}
}
else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass()))
{
FBoolProperty* const Property = CastField<FBoolProperty>(InnerProperty);
Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetBoolValue(AtIndex + TupleIndex));
}
else if (PropertyClass->IsChildOf(FStrProperty::StaticClass()))
{
FStrProperty* const Property = CastField<FStrProperty>(InnerProperty);
Property->SetPropertyValue(ValuePtr, InGenericAttribute.GetStringValue(AtIndex + TupleIndex));
}
else if (PropertyClass->IsChildOf(FNameProperty::StaticClass()))
{
FNameProperty* const Property = CastField<FNameProperty>(InnerProperty);
Property->SetPropertyValue(ValuePtr, FName(*InGenericAttribute.GetStringValue(AtIndex + TupleIndex)));
}
OnPropertyChanged(InnerProperty);
}
else
{
HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex);
if (TupleIndex == 0)
return false;
}
}
}
else if (FStructProperty* StructProperty = CastField<FStructProperty>(InnerProperty))
{
// struct properties
// If we receive an attribute with tuple size > 1 and the target is an Unreal struct property, then we set
// as many of the values as we can in the struct. For example: a 4-float received from Houdini where the
// target is an FVector, the FVector.X, Y and Z would be set from the 4-float indices 0-2. Index 3 from the
// 4-float would then be ignored.
const int32 TupleIndex = 0;
// If this is an array property, ensure it has enough space
// TODO: should we just set the array size to 1 for non-arrays and to the array size for arrays (once we support array attributes from Houdini)?
// vs just ensuring there is enough space (and then potentially leaving previous/old data behind?)
if (ArrayHelper.IsValid())
ArrayHelper->ExpandForIndex(TupleIndex);
void* PropertyValue = nullptr;
if (ArrayHelper.IsValid())
PropertyValue = ArrayHelper->GetRawPtr(TupleIndex);
else
PropertyValue = InnerProperty->ContainerPtrToValuePtr<void>(Container, TupleIndex);
if (PropertyValue)
{
const FName PropertyName = StructProperty->Struct->GetFName();
if (PropertyName == NAME_Vector)
{
// Found a vector property, fill it with up to 3 tuple values
FVector& Vector = *static_cast<FVector*>(PropertyValue);
Vector = FVector::ZeroVector;
Vector.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0);
if (InGenericAttribute.AttributeTupleSize > 1)
Vector.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1);
if (InGenericAttribute.AttributeTupleSize > 2)
Vector.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2);
OnPropertyChanged(StructProperty);
}
else if (PropertyName == NAME_Transform)
{
// Found a transform property fill it with the attribute tuple values
FVector Translation;
Translation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 0);
if (InGenericAttribute.AttributeTupleSize > 1)
Translation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1);
if (InGenericAttribute.AttributeTupleSize > 2)
Translation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2);
FQuat Rotation;
if (InGenericAttribute.AttributeTupleSize > 3)
Rotation.W = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3);
if (InGenericAttribute.AttributeTupleSize > 4)
Rotation.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 4);
if (InGenericAttribute.AttributeTupleSize > 5)
Rotation.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 5);
if (InGenericAttribute.AttributeTupleSize > 6)
Rotation.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 6);
FVector Scale(1, 1, 1);
if (InGenericAttribute.AttributeTupleSize > 7)
Scale.X = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 7);
if (InGenericAttribute.AttributeTupleSize > 8)
Scale.Y = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 8);
if (InGenericAttribute.AttributeTupleSize > 9)
Scale.Z = InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 9);
FTransform& Transform = *static_cast<FTransform*>(PropertyValue);
Transform = FTransform::Identity;
Transform.SetTranslation(Translation);
Transform.SetRotation(Rotation);
Transform.SetScale3D(Scale);
OnPropertyChanged(StructProperty);
}
else if (PropertyName == NAME_Color)
{
FColor& Color = *static_cast<FColor*>(PropertyValue);
Color = FColor::Black;
Color.R = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex);
if (InGenericAttribute.AttributeTupleSize > 1)
Color.G = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1);
if (InGenericAttribute.AttributeTupleSize > 2)
Color.B = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 2);
if (InGenericAttribute.AttributeTupleSize > 3)
Color.A = (int8)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 3);
OnPropertyChanged(StructProperty);
}
else if (PropertyName == NAME_LinearColor)
{
FLinearColor& LinearColor = *static_cast<FLinearColor*>(PropertyValue);
LinearColor = FLinearColor::Black;
LinearColor.R = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex);
if (InGenericAttribute.AttributeTupleSize > 1)
LinearColor.G = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1);
if (InGenericAttribute.AttributeTupleSize > 2)
LinearColor.B = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 2);
if (InGenericAttribute.AttributeTupleSize > 3)
LinearColor.A = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 3);
OnPropertyChanged(StructProperty);
}
else if (PropertyName == "Int32Interval")
{
FInt32Interval& Interval = *static_cast<FInt32Interval*>(PropertyValue);
Interval = FInt32Interval();
Interval.Min = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex);
if (InGenericAttribute.AttributeTupleSize > 1)
Interval.Max = (int32)InGenericAttribute.GetIntValue(AtIndex + TupleIndex + 1);
OnPropertyChanged(StructProperty);
}
else if (PropertyName == "FloatInterval")
{
FFloatInterval& Interval = *static_cast<FFloatInterval*>(PropertyValue);
Interval = FFloatInterval();
Interval.Min = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex);
if (InGenericAttribute.AttributeTupleSize > 1)
Interval.Max = (float)InGenericAttribute.GetDoubleValue(AtIndex + TupleIndex + 1);
OnPropertyChanged(StructProperty);
}
else
{
HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString());
return false;
}
}
else
{
HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName);
return false;
}
}
else if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(InnerProperty))
{
// OBJECT PATH PROPERTY
const int32 TupleIndex = 0;
// If this is an array property, ensure it has enough space
// TODO: should we just set the array size to 1 for non-arrays or to the array size for arrays (once we support array attributes from Houdini)?
// vs just ensuring there is enough space (and then potentially leaving previous/old data behind?)
if (ArrayHelper.IsValid())
ArrayHelper->ExpandForIndex(TupleIndex);
FString Value = InGenericAttribute.GetStringValue(AtIndex + TupleIndex);
void* ValuePtr = nullptr;
if (ArrayHelper.IsValid())
ValuePtr = ArrayHelper->GetRawPtr(TupleIndex);
else
ValuePtr = InnerProperty->ContainerPtrToValuePtr<FString>(Container, TupleIndex);
if (ValuePtr)
{
TSoftObjectPtr<UObject> ValueObjectPtr;
ValueObjectPtr = Value;
UObject* ValueObject = ValueObjectPtr.LoadSynchronous();
// Ensure the ObjectProperty class matches the ValueObject that we just loaded
if (!ValueObject || (ValueObject && ValueObject->IsA(ObjectProperty->PropertyClass)))
{
ObjectProperty->SetObjectPropertyValue(ValuePtr, ValueObject);
OnPropertyChanged(ObjectProperty);
}
else
{
HOUDINI_LOG_WARNING(
TEXT("Could net set object property %s: ObjectProperty's object class (%s) does not match referenced object class (%s)!"),
*InGenericAttribute.AttributeName, *(ObjectProperty->PropertyClass->GetName()), IsValid(ValueObject) ? *(ValueObject->GetClass()->GetName()) : TEXT("NULL"));
return false;
}
}
else
{
HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName);
return false;
}
}
else
{
// Property was found, but is of an unsupported type
HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName);
return false;
}
if (bHasModifiedProperty)
{
#if WITH_EDITOR
InObject->PostEditChange();
if (InOwner)
{
InOwner->PostEditChange();
}
#endif
}
return true;
}
bool
FHoudiniGenericAttribute::GetPropertyValueFromObject(
UObject* InObject,
FProperty* InFoundProperty,
void* InContainer,
FHoudiniGenericAttribute& InGenericAttribute,
const int32& InAtIndex)
{
if (!InObject || InObject->IsPendingKill() || !InFoundProperty)
return false;
// Determine the container to use (either InContainer if specified, or InObject)
void* Container = InContainer ? InContainer : InObject;
// Property class name, used for logging
const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown");
// Initialize using the found property
FProperty* InnerProperty = InFoundProperty;
FArrayProperty* ArrayProperty = CastField<FArrayProperty>(InFoundProperty);
TSharedPtr<FScriptArrayHelper_InContainer> ArrayHelper;
if (ArrayProperty)
{
InnerProperty = ArrayProperty->Inner;
ArrayHelper = MakeShareable<FScriptArrayHelper_InContainer>(new FScriptArrayHelper_InContainer(ArrayProperty, Container));
}
// TODO: implement support for array attributes received from Houdini
// Get the "proper" AtIndex in the flat array by using the attribute tuple size
// TODO: fix the issue when changing array of tuple properties!
const int32 TupleSize = InGenericAttribute.AttributeTupleSize;
int32 AtIndex = InAtIndex * TupleSize;
FFieldClass* PropertyClass = InnerProperty->GetClass();
if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) ||
PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass()))
{
// Supported non-struct properties
// If the attribute from Houdini has a tuple size > 1, we support getting it on arrays on the unreal side
// For example: a 3 float in Houdini can be set from a TArray<float> in Unreal.
for (int32 TupleIndex = 0; TupleIndex < TupleSize; ++TupleIndex)
{
void* ValuePtr = nullptr;
if (ArrayHelper.IsValid())
{
// Check that we are not out of range
if (TupleIndex >= ArrayHelper->Num())
break;
ValuePtr = ArrayHelper->GetRawPtr(TupleIndex);
}
else
{
// If this is not an ArrayProperty, it could be a fixed (standard C/C++ array), check the ArrayDim
// on the property to determine if our TupleIndex is in range, if not, give up, we cannot get any more
// of our tuple indices from this property.
if (TupleIndex >= InnerProperty->ArrayDim)
break;
ValuePtr = InnerProperty->ContainerPtrToValuePtr<void*>(Container, TupleIndex);
}
if (ValuePtr)
{
// Handle each property type that we support
if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()))
{
// Numeric properties are supported as floats and ints, and can also be set from a received string
FNumericProperty* const Property = CastField<FNumericProperty>(InnerProperty);
if (InGenericAttribute.AttributeType == EAttribStorageType::STRING)
{
InGenericAttribute.SetStringValue(Property->GetNumericPropertyValueToString(ValuePtr), AtIndex + TupleIndex);
}
else if (Property->IsFloatingPoint())
{
InGenericAttribute.SetDoubleValue(Property->GetFloatingPointPropertyValue(ValuePtr), AtIndex + TupleIndex);
}
else if (Property->IsInteger())
{
InGenericAttribute.SetIntValue(Property->GetSignedIntPropertyValue(ValuePtr), AtIndex + TupleIndex);
}
else
{
HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName);
return false;
}
}
else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass()))
{
FBoolProperty* const Property = CastField<FBoolProperty>(InnerProperty);
InGenericAttribute.SetBoolValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex);
}
else if (PropertyClass->IsChildOf(FStrProperty::StaticClass()))
{
FStrProperty* const Property = CastField<FStrProperty>(InnerProperty);
InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr), AtIndex + TupleIndex);
}
else if (PropertyClass->IsChildOf(FNameProperty::StaticClass()))
{
FNameProperty* const Property = CastField<FNameProperty>(InnerProperty);
InGenericAttribute.SetStringValue(Property->GetPropertyValue(ValuePtr).ToString(), AtIndex + TupleIndex);
}
}
else
{
HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s), tuple index %i"), *InGenericAttribute.AttributeName, *PropertyClassName, TupleIndex);
if (TupleIndex == 0)
return false;
}
}
}
else if (FStructProperty* StructProperty = CastField<FStructProperty>(InnerProperty))
{
// struct properties
// Set as many as the tuple values as we can from the Unreal struct.
const int32 TupleIndex = 0;
void* PropertyValue = nullptr;
if (ArrayHelper.IsValid())
{
if (ArrayHelper->IsValidIndex(TupleIndex))
PropertyValue = ArrayHelper->GetRawPtr(TupleIndex);
}
else if (TupleIndex < InnerProperty->ArrayDim)
{
PropertyValue = InnerProperty->ContainerPtrToValuePtr<void>(Container, TupleIndex);
}
if (PropertyValue)
{
const FName PropertyName = StructProperty->Struct->GetFName();
if (PropertyName == NAME_Vector)
{
// Found a vector property, fill it with up to 3 tuple values
const FVector& Vector = *static_cast<FVector*>(PropertyValue);
InGenericAttribute.SetDoubleValue(Vector.X, AtIndex + TupleIndex + 0);
if (InGenericAttribute.AttributeTupleSize > 1)
InGenericAttribute.SetDoubleValue(Vector.Y, AtIndex + TupleIndex + 1);
if (InGenericAttribute.AttributeTupleSize > 2)
InGenericAttribute.SetDoubleValue(Vector.Z, AtIndex + TupleIndex + 2);
}
else if (PropertyName == NAME_Transform)
{
// Found a transform property fill it with the attribute tuple values
const FTransform& Transform = *static_cast<FTransform*>(PropertyValue);
const FVector Translation = Transform.GetTranslation();
const FQuat Rotation = Transform.GetRotation();
const FVector Scale = Transform.GetScale3D();
InGenericAttribute.SetDoubleValue(Translation.X, AtIndex + TupleIndex + 0);
if (InGenericAttribute.AttributeTupleSize > 1)
InGenericAttribute.SetDoubleValue(Translation.Y, AtIndex + TupleIndex + 1);
if (InGenericAttribute.AttributeTupleSize > 2)
InGenericAttribute.SetDoubleValue(Translation.Z, AtIndex + TupleIndex + 2);
if (InGenericAttribute.AttributeTupleSize > 3)
InGenericAttribute.SetDoubleValue(Rotation.W, AtIndex + TupleIndex + 3);
if (InGenericAttribute.AttributeTupleSize > 4)
InGenericAttribute.SetDoubleValue(Rotation.X, AtIndex + TupleIndex + 4);
if (InGenericAttribute.AttributeTupleSize > 5)
InGenericAttribute.SetDoubleValue(Rotation.Y, AtIndex + TupleIndex + 5);
if (InGenericAttribute.AttributeTupleSize > 6)
InGenericAttribute.SetDoubleValue(Rotation.Z, AtIndex + TupleIndex + 6);
if (InGenericAttribute.AttributeTupleSize > 7)
InGenericAttribute.SetDoubleValue(Scale.X, AtIndex + TupleIndex + 7);
if (InGenericAttribute.AttributeTupleSize > 8)
InGenericAttribute.SetDoubleValue(Scale.Y, AtIndex + TupleIndex + 8);
if (InGenericAttribute.AttributeTupleSize > 9)
InGenericAttribute.SetDoubleValue(Scale.Z, AtIndex + TupleIndex + 9);
}
else if (PropertyName == NAME_Color)
{
const FColor& Color = *static_cast<FColor*>(PropertyValue);
InGenericAttribute.SetIntValue(Color.R, AtIndex + TupleIndex);
if (InGenericAttribute.AttributeTupleSize > 1)
InGenericAttribute.SetIntValue(Color.G, AtIndex + TupleIndex + 1);
if (InGenericAttribute.AttributeTupleSize > 2)
InGenericAttribute.SetIntValue(Color.B, AtIndex + TupleIndex + 2);
if (InGenericAttribute.AttributeTupleSize > 3)
InGenericAttribute.SetIntValue(Color.A, AtIndex + TupleIndex + 3);
}
else if (PropertyName == NAME_LinearColor)
{
const FLinearColor& LinearColor = *static_cast<FLinearColor*>(PropertyValue);
InGenericAttribute.SetDoubleValue(LinearColor.R, AtIndex + TupleIndex);
if (InGenericAttribute.AttributeTupleSize > 1)
InGenericAttribute.SetDoubleValue(LinearColor.G, AtIndex + TupleIndex + 1);
if (InGenericAttribute.AttributeTupleSize > 2)
InGenericAttribute.SetDoubleValue(LinearColor.B, AtIndex + TupleIndex + 2);
if (InGenericAttribute.AttributeTupleSize > 3)
InGenericAttribute.SetDoubleValue(LinearColor.A, AtIndex + TupleIndex + 3);
}
else if (PropertyName == "Int32Interval")
{
const FInt32Interval& Interval = *static_cast<FInt32Interval*>(PropertyValue);
InGenericAttribute.SetIntValue(Interval.Min, AtIndex + TupleIndex);
if (InGenericAttribute.AttributeTupleSize > 1)
InGenericAttribute.SetIntValue(Interval.Max, AtIndex + TupleIndex + 1);
}
else if (PropertyName == "FloatInterval")
{
const FFloatInterval& Interval = *static_cast<FFloatInterval*>(PropertyValue);
InGenericAttribute.SetDoubleValue(Interval.Min, AtIndex + TupleIndex);
if (InGenericAttribute.AttributeTupleSize > 1)
InGenericAttribute.SetDoubleValue(Interval.Max, AtIndex + TupleIndex + 1);
}
else
{
HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InGenericAttribute.AttributeName, *PropertyClassName, *PropertyName.ToString());
return false;
}
}
else
{
HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName);
return false;
}
}
else if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(InnerProperty))
{
// OBJECT PATH PROPERTY
const int32 TupleIndex = 0;
void* ValuePtr = nullptr;
if (ArrayHelper.IsValid())
{
if (ArrayHelper->IsValidIndex(TupleIndex))
ValuePtr = ArrayHelper->GetRawPtr(TupleIndex);
}
else if (TupleIndex < InnerProperty->ArrayDim)
{
ValuePtr = InnerProperty->ContainerPtrToValuePtr<void>(Container, TupleIndex);
}
if (ValuePtr)
{
UObject* ValueObject = ObjectProperty->GetObjectPropertyValue(ValuePtr);
const TSoftObjectPtr<UObject> ValueObjectPtr = ValueObject;
InGenericAttribute.SetStringValue(ValueObjectPtr.ToString(), AtIndex + TupleIndex);
}
else
{
HOUDINI_LOG_WARNING(TEXT("Could net get a valid value ptr for uproperty %s (Class %s)"), *InGenericAttribute.AttributeName, *PropertyClassName);
return false;
}
}
else
{
// Property was found, but is of an unsupported type
HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InGenericAttribute.AttributeName);
return false;
}
return true;
}
bool
FHoudiniGenericAttribute::GetAttributeTupleSizeAndStorageFromProperty(
UObject* InObject,
FProperty* InFoundProperty,
void* InContainer,
int32& OutAttributeTupleSize,
EAttribStorageType& OutAttributeStorageType)
{
if (!InObject || InObject->IsPendingKill() || !InFoundProperty)
return false;
// Determine the container to use (either InContainer if specified, or InObject)
void* Container = InContainer ? InContainer : InObject;
// Property class name, used for logging
const FString PropertyClassName = InFoundProperty->GetClass() ? InFoundProperty->GetClass()->GetName() : TEXT("Unknown");
// Initialize using the found property
FProperty* InnerProperty = InFoundProperty;
// FArrayProperty* ArrayProperty = CastField<FArrayProperty>(InFoundProperty);
// TSharedPtr<FScriptArrayHelper_InContainer> ArrayHelper;
// if (ArrayProperty)
// {
// InnerProperty = ArrayProperty->Inner;
// ArrayHelper = MakeShareable<FScriptArrayHelper_InContainer>(new FScriptArrayHelper_InContainer(ArrayProperty, Container));
// }
FFieldClass* PropertyClass = InnerProperty->GetClass();
if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()) || PropertyClass->IsChildOf(FBoolProperty::StaticClass()) ||
PropertyClass->IsChildOf(FStrProperty::StaticClass()) || PropertyClass->IsChildOf(FNameProperty::StaticClass()))
{
// Supported non-struct properties
// Here we cannot really do better than tuple size of 1 (since we need to support arrays in the future, we
// cannot just assume array size == tuple size going to Houdini)
OutAttributeTupleSize = 1;
// Handle each property type that we support
if (PropertyClass->IsChildOf(FNumericProperty::StaticClass()))
{
// Numeric properties are supported as floats and ints, and can also be set from a received string
FNumericProperty* const Property = CastField<FNumericProperty>(InnerProperty);
if (Property->IsFloatingPoint())
{
OutAttributeStorageType = EAttribStorageType::FLOAT;
}
else if (Property->IsInteger())
{
OutAttributeStorageType = EAttribStorageType::INT;
}
else
{
HOUDINI_LOG_WARNING(TEXT("Unsupported numeric property for %s (Class %s)"), *Property->GetName(), *PropertyClassName);
return false;
}
}
else if (PropertyClass->IsChildOf(FBoolProperty::StaticClass()))
{
OutAttributeStorageType = EAttribStorageType::INT;
}
else if (PropertyClass->IsChildOf(FStrProperty::StaticClass()))
{
OutAttributeStorageType = EAttribStorageType::STRING;
}
else if (PropertyClass->IsChildOf(FNameProperty::StaticClass()))
{
OutAttributeStorageType = EAttribStorageType::STRING;
}
}
else if (FStructProperty* StructProperty = CastField<FStructProperty>(InnerProperty))
{
// struct properties
const FName PropertyName = StructProperty->Struct->GetFName();
if (PropertyName == NAME_Vector)
{
OutAttributeTupleSize = 3;
OutAttributeStorageType = EAttribStorageType::FLOAT;
}
else if (PropertyName == NAME_Transform)
{
OutAttributeTupleSize = 10;
OutAttributeStorageType = EAttribStorageType::FLOAT;
}
else if (PropertyName == NAME_Color)
{
OutAttributeTupleSize = 4;
OutAttributeStorageType = EAttribStorageType::INT;
}
else if (PropertyName == NAME_LinearColor)
{
OutAttributeTupleSize = 4;
OutAttributeStorageType = EAttribStorageType::FLOAT;
}
else if (PropertyName == "Int32Interval")
{
OutAttributeTupleSize = 2;
OutAttributeStorageType = EAttribStorageType::INT;
}
else if (PropertyName == "FloatInterval")
{
OutAttributeTupleSize = 2;
OutAttributeStorageType = EAttribStorageType::FLOAT;
}
else
{
HOUDINI_LOG_WARNING(TEXT("For uproperty %s (Class %s): unsupported struct property type: %s"), *InFoundProperty->GetName(), *PropertyClassName, *PropertyName.ToString());
return false;
}
}
else if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(InnerProperty))
{
OutAttributeTupleSize = 1;
OutAttributeStorageType = EAttribStorageType::STRING;
}
else
{
// Property was found, but is of an unsupported type
HOUDINI_LOG_WARNING(TEXT("Unsupported UProperty Class: %s found for uproperty %s"), *PropertyClassName, *InFoundProperty->GetName());
return false;
}
return true;
}
/*
bool
FHoudiniEngineUtils::TryToFindInStructProperty(
UObject* Object, FString UPropertyNameToFind, UStructProperty* StructProperty, UProperty*& FoundProperty, void*& StructContainer )
{
if ( !StructProperty || !Object )
return false;
// Walk the structs' properties and try to find the one we're looking for
UScriptStruct* Struct = StructProperty->Struct;
for (TFieldIterator< UProperty > It(Struct); It; ++It)
{
UProperty* Property = *It;
if ( !Property )
continue;
FString DisplayName = It->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT(""));
FString Name = It->GetName();
// If the property name contains the uprop attribute name, we have a candidate
if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) )
{
// We found the property in the struct property, we need to keep the ValuePtr in the object
// of the structProp in order to be able to access the property value afterwards...
FoundProperty = Property;
StructContainer = StructProperty->ContainerPtrToValuePtr< void >( Object, 0);
// If it's an equality, we dont need to keep searching
if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) )
return true;
}
if ( FoundProperty )
continue;
UStructProperty* NestedStruct = Cast<UStructProperty>( Property );
if ( NestedStruct && TryToFindInStructProperty( Object, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) )
return true;
UArrayProperty* ArrayProp = Cast<UArrayProperty>( Property );
if ( ArrayProp && TryToFindInArrayProperty( Object, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) )
return true;
}
return false;
}
bool
FHoudiniEngineUtils::TryToFindInArrayProperty(
UObject* Object, FString UPropertyNameToFind, UArrayProperty* ArrayProperty, UProperty*& FoundProperty, void*& StructContainer )
{
if ( !ArrayProperty || !Object )
return false;
UProperty* Property = ArrayProperty->Inner;
if ( !Property )
return false;
FString DisplayName = Property->GetDisplayNameText().ToString().Replace(TEXT(" "), TEXT(""));
FString Name = Property->GetName();
// If the property name contains the uprop attribute name, we have a candidate
if ( Name.Contains( UPropertyNameToFind ) || DisplayName.Contains( UPropertyNameToFind ) )
{
// We found the property in the struct property, we need to keep the ValuePtr in the object
// of the structProp in order to be able to access the property value afterwards...
FoundProperty = Property;
StructContainer = ArrayProperty->ContainerPtrToValuePtr< void >( Object, 0);
// If it's an equality, we dont need to keep searching
if ( ( Name == UPropertyNameToFind ) || ( DisplayName == UPropertyNameToFind ) )
return true;
}
if ( !FoundProperty )
{
UStructProperty* NestedStruct = Cast<UStructProperty>( Property );
if ( NestedStruct && TryToFindInStructProperty( ArrayProperty, UPropertyNameToFind, NestedStruct, FoundProperty, StructContainer ) )
return true;
UArrayProperty* ArrayProp = Cast<UArrayProperty>( Property );
if ( ArrayProp && TryToFindInArrayProperty( ArrayProperty, UPropertyNameToFind, ArrayProp, FoundProperty, StructContainer ) )
return true;
}
return false;
}
*/