Files
UnrealEngine/Engine/Plugins/FX/Niagara/Source/NiagaraEditor/Private/NiagaraNodeConvert.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

924 lines
28 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NiagaraNodeConvert.h"
#include "EdGraphSchema_Niagara.h"
#include "NiagaraEditorUtilities.h"
#include "Widgets/SNiagaraGraphNodeConvert.h"
#include "NiagaraHlslTranslator.h"
#include "UObject/UnrealType.h"
#include "Kismet2/StructureEditorUtils.h"
#include "ScopedTransaction.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(NiagaraNodeConvert)
#define LOCTEXT_NAMESPACE "NiagaraNodeConvert"
struct FNiagaraConvertEntry
{
bool bConnected = false;
FGuid PinId;
FName Name;
FNiagaraTypeDefinition Type;
TArray< FNiagaraConvertEntry> Children;
UEdGraphPin* Pin;
FNiagaraConvertEntry(const FGuid& InPinId, const FName& InName, const FNiagaraTypeDefinition& InType, UEdGraphPin* InPin): PinId(InPinId), Name(InName), Pin(InPin){ }
void ResolveConnections(const TArray<FNiagaraConvertConnection>& InConnections, TArray<FString>& OutMissingConnections, int32 ConnectionDepth = 0)
{
TArray<FNiagaraConvertConnection> CandidateConnections;
for (const FNiagaraConvertConnection& Connection : InConnections)
{
if (Connection.DestinationPinId == PinId)
{
// If connecting root to root, we are fine.
if (ConnectionDepth == 0 && Connection.DestinationPath.Num() == 0)
{
bConnected = true;
break;
}
else if ((Connection.DestinationPath.Num() == (ConnectionDepth+1) && Connection.DestinationPath[ConnectionDepth] == Name))
{
bConnected = true;
break;
}
if (ConnectionDepth == 0 || (ConnectionDepth > 0 && Connection.DestinationPath.Num() > ConnectionDepth && Connection.DestinationPath[ConnectionDepth] == Name))
{
CandidateConnections.Add(Connection);
}
}
}
if (bConnected == true)
{
return;
}
// Now see if all children are connected and then return that you are connected.
if (Children.Num() > 0)
{
if (CandidateConnections.Num() > 0)
{
TArray <FString> MissingConnectionsChildren;
int32 NumConnected = 0;
for (FNiagaraConvertEntry& Entry : Children)
{
Entry.ResolveConnections(CandidateConnections, MissingConnectionsChildren, 1 + ConnectionDepth);
if (Entry.bConnected)
{
NumConnected++;
}
}
if (NumConnected == Children.Num())
{
bConnected = true;
}
else
{
for (const FString& MissingConnectionStr : MissingConnectionsChildren)
{
OutMissingConnections.Emplace(Name.ToString() + TEXT(".") + MissingConnectionStr);
}
}
}
else
{
OutMissingConnections.Emplace(Name.ToString());
}
}
else
{
OutMissingConnections.Emplace(Name.ToString());
}
}
static void CreateEntries(const UEdGraphSchema_Niagara* Schema, const FGuid& InPinId, UEdGraphPin* InPin, const UScriptStruct* InStruct, TArray< FNiagaraConvertEntry>& OutEntries)
{
check(Schema);
for (TFieldIterator<FProperty> PropertyIt(InStruct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
{
const FProperty* Property = *PropertyIt;
if (Schema->IsValidNiagaraPropertyType(Property))
{
FNiagaraTypeDefinition PropType = Schema->GetTypeDefForProperty(Property);
int32 Index = OutEntries.Emplace(InPinId, Property->GetFName(), PropType, InPin);
const FStructProperty* StructProperty = CastField<FStructProperty>(Property);
if (StructProperty != nullptr)
{
CreateEntries(Schema, InPinId, InPin, FNiagaraTypeHelper::FindNiagaraFriendlyTopLevelStruct(StructProperty->Struct, ENiagaraStructConversion::UserFacing), OutEntries[Index].Children);
}
}
}
}
};
UNiagaraNodeConvert::UNiagaraNodeConvert() : UNiagaraNodeWithDynamicPins(), bIsWiringShown(true)
{
}
void UNiagaraNodeConvert::PostLoad()
{
Super::PostLoad();
if (IsLocalConstantValue())
{
// collapse convert nodes with simple constant values on load
SetWiringShown(false);
}
}
void UNiagaraNodeConvert::AllocateDefaultPins()
{
CreateAddPin(EGPD_Input);
CreateAddPin(EGPD_Output);
}
TSharedPtr<SGraphNode> UNiagaraNodeConvert::CreateVisualWidget()
{
return SNew(SNiagaraGraphNodeConvert, this);
}
void UNiagaraNodeConvert::Compile(FTranslator* Translator, TArray<int32>& CompileOutputs) const
{
FPinCollectorArray InputPins;
GetCompilationInputPins(InputPins);
TArray<int32, TInlineAllocator<16>> CompileInputs;
CompileInputs.Reserve(InputPins.Num());
for(UEdGraphPin* InputPin : InputPins)
{
if (InputPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryType ||
InputPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryEnum ||
InputPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryStaticType ||
InputPin->PinType.PinCategory == UEdGraphSchema_Niagara::PinCategoryStaticEnum)
{
int32 CompiledInput = Translator->CompileInputPin(InputPin);
if (CompiledInput == INDEX_NONE)
{
Translator->Error(LOCTEXT("InputError", "Error compiling input for convert node."), this, InputPin);
}
CompileInputs.Add(CompiledInput);
}
}
const UEdGraphSchema_Niagara* Schema = CastChecked<UEdGraphSchema_Niagara>(GetSchema());
check(Schema);
FPinCollectorArray OutputPins;
GetCompilationOutputPins(OutputPins);
// Go through all the output nodes and cross-reference them with the connections list. Output errors if any connections are incomplete.
{
TArray< FNiagaraConvertEntry> Entries;
for (UEdGraphPin* OutputPin : OutputPins)
{
FNiagaraTypeDefinition TypeDef;
if (OutputPin && OutputPin->HasAnyConnections())
{
TypeDef = Schema->PinToTypeDefinition(OutputPin);
const UScriptStruct* Struct = TypeDef.GetScriptStruct();
if (Struct)
{
FNiagaraConvertEntry::CreateEntries(Schema, OutputPin->PinId, OutputPin, Struct, Entries);
}
}
}
for (FNiagaraConvertEntry& Entry : Entries)
{
TArray<FString> MissingConnections;
Entry.ResolveConnections(Connections, MissingConnections);
if (Entry.bConnected == false)
{
for (const FString& MissedConnection : MissingConnections)
{
Translator->Error(FText::Format(LOCTEXT("MissingOutputPinConnection", "Missing internal connection for output pin slot: {0}"),
FText::FromString(MissedConnection)), this, Entry.Pin);
}
}
}
}
Translator->Convert(this, CompileInputs, CompileOutputs);
}
FString FNiagaraConvertConnection::ToString() const
{
FString SrcName;
FString DestName;
for (const FName& Src : SourcePath)
{
SrcName += TEXT("/") + Src.ToString();
}
for (const FName& Dest : DestinationPath)
{
DestName += TEXT("/") + Dest.ToString();
}
return SrcName + TEXT(" to ") + DestName;
}
namespace FNiagaraConvertRefreshHelpers
{
// Helper function to reduce book-keeping around searching by predicate
UEdGraphPin* FindPinByID(FGuid SearchPinId, TArray<UEdGraphPin*> PinsToSearch)
{
UEdGraphPin** FoundPin = PinsToSearch.FindByPredicate([&SearchPinId](UEdGraphPin* Pin) {return Pin->PinId == SearchPinId; });
if (FoundPin)
{
return *FoundPin;
}
return nullptr;
}
// Helper function that renames a pin if it matches a member in the given struct
void RenamePinIfInStruct(UEdGraphPin* Pin, const UScriptStruct* Struct, FGuid& ConnectionGuid)
{
const UUserDefinedStruct* UserDefinedStruct = Cast<const UUserDefinedStruct>(Struct);
for (FProperty* Property : TFieldRange<FProperty>(Struct, EFieldIteratorFlags::IncludeSuper))
{
FString PropertyName;
FGuid PropertyGuid = FStructureEditorUtils::GetGuidForProperty(Property);
// User defined structs will have GUIDs as part of the name, so the friendly name is needed instead
if (UserDefinedStruct)
{
PropertyName = FStructureEditorUtils::GetVariableFriendlyName(UserDefinedStruct, PropertyGuid);
}
else
{
PropertyName = Property->GetName();
}
// Rename and store GUID
if (Pin->GetName() == PropertyName || (ConnectionGuid == PropertyGuid && ConnectionGuid != FGuid()))
{
ConnectionGuid = PropertyGuid;
Pin->PinName = FName(*PropertyName);
break;
}
}
}
// Recurses paths of a convert connection, and renames if the path is valid to conform to changes in the underlying struct
// Returns true if the path is valid, if a property can be found, its pointer is stored in OutProperty.
bool RecursePaths(const UScriptStruct* ParentStruct, TArray<FName>& Paths, int32 PathIndex, FProperty*& OutProperty)
{
bool bResult = false;
if (Paths.IsValidIndex(PathIndex))
{
FName CurrentPath = Paths[PathIndex];
// Paths will end with "Value" for scalars, which is a special case. The path is valid, but there's nothing more to search for
if (CurrentPath.ToString().Equals("Value"))
{
return true;
}
FGuid CurrentGUID = FStructureEditorUtils::GetGuidFromPropertyName(CurrentPath);
FProperty* FoundProperty = nullptr;
// Search for path by name or GUID in struct
for (FProperty* Property : TFieldRange<FProperty>(ParentStruct))
{
FGuid PropertyGuid = FStructureEditorUtils::GetGuidFromPropertyName(Property->GetFName());
if (CurrentPath == Property->GetFName() || (PropertyGuid != FGuid() && PropertyGuid == CurrentGUID))
{
FoundProperty = Property;
break;
}
}
if (FoundProperty)
{
// Path is valid up until this point
bResult = true;
const FStructProperty* StructProperty = CastField<const FStructProperty>(FoundProperty);
// For nested structs update the parent struct to this property
if (StructProperty)
{
ParentStruct = StructProperty->Struct;
}
// Continue if there are more entries
if (Paths.IsValidIndex(PathIndex + 1))
{
bResult = RecursePaths(ParentStruct, Paths, PathIndex + 1, OutProperty);
}
if (bResult)
{
// If no other calls have assigned the out property, do so here
if (!OutProperty)
{
OutProperty = FoundProperty;
}
// Fix up the name here.
Paths[PathIndex] = FoundProperty->GetFName();
}
}
}
else if (Paths.Num() == 0)
{
// Top-level struct connections will have an empty paths array.
return true;
}
return bResult;
}
}
bool UNiagaraNodeConvert::RefreshFromExternalChanges()
{
bool bHasChanged = false;
// Used to get struct definition
const UEdGraphSchema_Niagara* Schema = CastChecked<UEdGraphSchema_Niagara>(GetSchema());
check(Schema);
const UScriptStruct* SourceScriptStruct;
const UScriptStruct* DestinationScriptStruct;
// Cache connections before clean-up for comparison to check if anything changed/a recompile is necessary.
const TArray<FNiagaraConvertConnection> OldConnections = Connections;
// Clean up paths in each connection. Remove or mark orphaned pins, and remove connections as needed.
for (int32 ConnectionIndex = 0; ConnectionIndex < Connections.Num(); ConnectionIndex++)
{
FNiagaraConvertConnection Connection = Connections[ConnectionIndex];
UEdGraphPin* SourcePin = FNiagaraConvertRefreshHelpers::FindPinByID(Connection.SourcePinId, Pins);
UEdGraphPin* DestinationPin = FNiagaraConvertRefreshHelpers::FindPinByID(Connection.DestinationPinId, Pins);
// Get type definitions for the pins in this connection
// PinToTypeDefinition handles nullptr case, so no need to null check them here
FNiagaraTypeDefinition SourcePinTypeDef = Schema->PinToTypeDefinition(SourcePin);
FNiagaraTypeDefinition DestinationPinTypeDef = Schema->PinToTypeDefinition(DestinationPin);
SourceScriptStruct = SourcePinTypeDef.GetScriptStruct();
DestinationScriptStruct = DestinationPinTypeDef.GetScriptStruct();
bool bIsSourceScalar = FNiagaraTypeDefinition::IsScalarDefinition(SourcePinTypeDef);
bool bIsDestinationScalar = FNiagaraTypeDefinition::IsScalarDefinition(DestinationPinTypeDef);
// Rename pins and fix up stored GUID if found in struct
// If both pins are structs, search both for the other pin, rename pin, and set GUID as necessary.
if (!bIsSourceScalar && !bIsDestinationScalar)
{
FGuid InOutSourceGuidToSearch = Connection.SourcePropertyId;
FGuid InOutDestGuidToSearch = Connection.DestinationPropertyId;
FNiagaraConvertRefreshHelpers::RenamePinIfInStruct(SourcePin, DestinationScriptStruct, InOutSourceGuidToSearch);
FNiagaraConvertRefreshHelpers::RenamePinIfInStruct(DestinationPin, SourceScriptStruct, InOutDestGuidToSearch);
if (InOutSourceGuidToSearch != FGuid())
{
Connection.SourcePropertyId = InOutSourceGuidToSearch;
}
if (InOutDestGuidToSearch != FGuid())
{
Connection.DestinationPropertyId = InOutDestGuidToSearch;
}
}
else if (bIsSourceScalar && !bIsDestinationScalar)
{
FGuid InOutGuidToSearch = Connection.SourcePropertyId;
FNiagaraConvertRefreshHelpers::RenamePinIfInStruct(SourcePin, DestinationScriptStruct, InOutGuidToSearch);
if (InOutGuidToSearch != FGuid())
{
Connection.SourcePropertyId = InOutGuidToSearch;
}
}
else if (!bIsSourceScalar && bIsDestinationScalar)
{
FGuid InOutGuidToSearch = Connection.DestinationPropertyId;
FNiagaraConvertRefreshHelpers::RenamePinIfInStruct(DestinationPin, SourceScriptStruct, InOutGuidToSearch);
if (InOutGuidToSearch != FGuid())
{
Connection.DestinationPropertyId = InOutGuidToSearch;
}
}
// Need to initialize as nullptr for RecursePaths
FProperty* SourceProperty = nullptr;
FProperty* DestinationProperty = nullptr;
bool bSourceFound = FNiagaraConvertRefreshHelpers::RecursePaths(SourceScriptStruct, Connection.SourcePath, 0, SourceProperty);
bool bDestinationFound = FNiagaraConvertRefreshHelpers::RecursePaths(DestinationScriptStruct, Connection.DestinationPath, 0, DestinationProperty);
bool bTypesAreAssignable = false;
// Ensure types are still assignable.
if (bSourceFound && bDestinationFound)
{
// Use the property found by recursing paths if possible
// A null Source or Destination property means the path was empty, and we should use the pin's type.
FNiagaraTypeDefinition SourcePropTypeDef = SourceProperty ? Schema->GetTypeDefForProperty(SourceProperty) : SourcePinTypeDef;
FNiagaraTypeDefinition DestinationPropTypeDef = DestinationProperty ? Schema->GetTypeDefForProperty(DestinationProperty) : DestinationPinTypeDef;
bTypesAreAssignable = FNiagaraTypeDefinition::TypesAreAssignable(SourcePropTypeDef, DestinationPropTypeDef) || FNiagaraTypeDefinition::IsLossyConversion(SourcePropTypeDef, DestinationPropTypeDef);
}
// Scalar pins that are not connected should be orphaned
if (bIsSourceScalar && (!bDestinationFound || !bTypesAreAssignable))
{
SourcePin->bOrphanedPin = true;
}
// If they were orphaned, and now have a valid connection they are no longer orphan pins
else if (SourcePin->bOrphanedPin && bDestinationFound && bTypesAreAssignable)
{
SourcePin->bOrphanedPin = false;
}
// Repeat for destination pins
if (bIsDestinationScalar && (!bSourceFound || !bTypesAreAssignable))
{
DestinationPin->bOrphanedPin = true;
}
else if (DestinationPin->bOrphanedPin && bSourceFound && bTypesAreAssignable)
{
DestinationPin->bOrphanedPin = false;
}
// Remove invalid connections
if (!bDestinationFound || !bSourceFound || !bTypesAreAssignable)
{
Connections.RemoveAt(ConnectionIndex);
ConnectionIndex--;
}
else
{
// write updated connection to array.
Connections[ConnectionIndex] = Connection;
}
}
// Compare new and old connections to see if data has changed.
if (OldConnections.Num() == Connections.Num())
{
for (int32 ConnectionIndex = 0; ConnectionIndex < Connections.Num(); ConnectionIndex++)
{
FNiagaraConvertConnection OldConneciton = OldConnections[ConnectionIndex];
FNiagaraConvertConnection NewConnection = Connections[ConnectionIndex];
if (!(NewConnection == OldConneciton))
{
bHasChanged = true;
break;
}
}
}
else
{
bHasChanged = true;
}
// There are changes to the underlying struct that may not impact the connections stored, so we have no way of testing when a visual refresh is necessary.
// So assume the UI always needs to be refreshed if this function is called, since the user is triggering a node refresh explicitly.
// If the connections haven't changed, just update the UI, and do not recompile.
// Any necessary recompile and visual update will be handled by the schema, if the data has changed (i.e. this function returns true). See: UEdGraphSchema_Niagara::RefreshNode
if (!bHasChanged)
{
MarkNodeRequiresSynchronization(__FUNCTION__, false);
}
return bHasChanged;
}
void UNiagaraNodeConvert::AutowireNewNode(UEdGraphPin* FromPin)
{
const UEdGraphSchema_Niagara* Schema = CastChecked<UEdGraphSchema_Niagara>(GetSchema());
check(Schema);
FNiagaraTypeDefinition TypeDef;
EEdGraphPinDirection Dir = FromPin ? (EEdGraphPinDirection)FromPin->Direction : EGPD_Output;
EEdGraphPinDirection OppositeDir = FromPin && (EEdGraphPinDirection)FromPin->Direction == EGPD_Input ? EGPD_Output : EGPD_Input;
if (AutowireSwizzle.IsEmpty())
{
// we only allow breaking on output pins
if (AutowireBreakType.GetStruct() != nullptr)
{
TypeDef = AutowireBreakType;
Dir = EGPD_Output;
OppositeDir = EGPD_Input;
}
// but we allow making from output puts and for input pins
else if(AutowireMakeType.GetStruct() != nullptr)
{
TypeDef = AutowireMakeType;
Dir = EGPD_Input;
OppositeDir = EGPD_Output;
}
if (TypeDef.IsValid() == false)
{
return;
}
//No swizzle so we make or break the type.
const UScriptStruct* Struct = TypeDef.GetScriptStruct();
if (Struct)
{
bool bConnectionMade = false;
UEdGraphPin* ConnectPin = RequestNewTypedPin(OppositeDir, TypeDef);
check(ConnectPin);
if(FromPin)
{
// FromPin and ConnectPin could have the same direction in case we have a make type and we are dragging off from i.e. float to make vector
// if so, we won't connect that pin and instead will try to connect with the other pins below
bool bCanConnect = GetSchema()->CanCreateConnection(FromPin, ConnectPin).Response != ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW;
// if our from pin is an input pin, we make sure to break prior connections first
if (bCanConnect && FromPin->Direction == EGPD_Input && ConnectPin->Direction == EGPD_Output)
{
FromPin->BreakAllPinLinks();
}
if(bCanConnect)
{
ConnectPin->MakeLinkTo(FromPin);
bConnectionMade = true;
}
}
TArray<FName> SrcPath;
TArray<FName> DestPath;
//Add a corresponding pin for each property in the from Pin.
for (TFieldIterator<FProperty> PropertyIt(Struct, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
{
SrcPath.Empty(SrcPath.Num());
DestPath.Empty(DestPath.Num());
const FProperty* Property = *PropertyIt;
if (!Schema->IsValidNiagaraPropertyType(Property))
{
continue;
}
FNiagaraTypeDefinition PropType = Schema->GetTypeDefForProperty(Property);
UEdGraphPin* NewPin = RequestNewTypedPin(Dir, PropType, *Property->GetDisplayNameText().ToString());
if (Dir == EGPD_Input)
{
if(FromPin && FromPin->Direction == EGPD_Output && bConnectionMade == false)
{
FNiagaraTypeDefinition FromPinType = UEdGraphSchema_Niagara::PinToTypeDefinition(FromPin);
FNiagaraTypeDefinition InputPinType = UEdGraphSchema_Niagara::PinToTypeDefinition(NewPin);
if(FNiagaraTypeDefinition::TypesAreAssignable(InputPinType, FromPinType) && GetSchema()->CanCreateConnection(FromPin, NewPin).Response != ECanCreateConnectionResponse::CONNECT_RESPONSE_DISALLOW)
{
FromPin->MakeLinkTo(NewPin);
bConnectionMade = true;
}
}
if (FNiagaraTypeDefinition::IsScalarDefinition(PropType))
{
SrcPath.Add(TEXT("Value"));
}
DestPath.Add(*Property->GetName());
FGuid PropertyGuid = FStructureEditorUtils::GetGuidForProperty(Property);
Connections.Add(FNiagaraConvertConnection(NewPin->PinId, SrcPath, ConnectPin->PinId, DestPath, PropertyGuid, FGuid()));
if (SrcPath.Num())
{
AddExpandedRecord(FNiagaraConvertPinRecord(NewPin->PinId, SrcPath).GetParent());
}
if (DestPath.Num())
{
AddExpandedRecord(FNiagaraConvertPinRecord(ConnectPin->PinId, DestPath).GetParent());
}
}
else
{
SrcPath.Add(*Property->GetName());
if (FNiagaraTypeDefinition::IsScalarDefinition(PropType))
{
DestPath.Add(TEXT("Value"));
}
FGuid PropertyGuid = FStructureEditorUtils::GetGuidForProperty(Property);
Connections.Add(FNiagaraConvertConnection(ConnectPin->PinId, SrcPath, NewPin->PinId, DestPath, FGuid(), PropertyGuid));
if (DestPath.Num())
{
AddExpandedRecord(FNiagaraConvertPinRecord(NewPin->PinId, DestPath).GetParent());
}
if (SrcPath.Num())
{
AddExpandedRecord(FNiagaraConvertPinRecord(ConnectPin->PinId, SrcPath).GetParent());
}
}
}
}
SetWiringShown(false);
}
else
{
check(FromPin);
check(OppositeDir == EGPD_Input);
static FNiagaraTypeDefinition SwizTypes[4] =
{
FNiagaraTypeDefinition::GetFloatDef(),
FNiagaraTypeDefinition::GetVec2Def(),
FNiagaraTypeDefinition::GetVec3Def(),
FNiagaraTypeDefinition::GetVec4Def()
};
static FName SwizComponents[4] =
{
TEXT("X"),
TEXT("Y"),
TEXT("Z"),
TEXT("W")
};
// TypeDef won't be initialized for swizzles yet
TypeDef = Schema->PinToTypeDefinition(FromPin);
UEdGraphPin* ConnectPin = RequestNewTypedPin(OppositeDir, TypeDef);
check(ConnectPin);
ConnectPin->MakeLinkTo(FromPin);
check(AutowireSwizzle.Len() <= 4 && AutowireSwizzle.Len() > 0);
FNiagaraTypeDefinition SwizType = SwizTypes[AutowireSwizzle.Len() - 1];
UEdGraphPin* NewPin = RequestNewTypedPin(EGPD_Output, SwizType, *SwizType.GetNameText().ToString());
TArray<FName> SrcPath;
TArray<FName> DestPath;
for (int32 i = 0; i < AutowireSwizzle.Len(); ++i)
{
TCHAR Char = AutowireSwizzle[i];
FString CharStr = FString::ConstructFromPtrSize(&Char, 1);
SrcPath.Empty(SrcPath.Num());
DestPath.Empty(DestPath.Num());
SrcPath.Add(*CharStr);
DestPath.Add(FNiagaraTypeDefinition::IsScalarDefinition(SwizType) ? TEXT("Value") : SwizComponents[i]);
Connections.Add(FNiagaraConvertConnection(ConnectPin->PinId, SrcPath, NewPin->PinId, DestPath));
if (DestPath.Num())
{
AddExpandedRecord(FNiagaraConvertPinRecord(NewPin->PinId, DestPath).GetParent());
}
if (SrcPath.Num())
{
AddExpandedRecord(FNiagaraConvertPinRecord(ConnectPin->PinId, SrcPath).GetParent());
}
}
}
MarkNodeRequiresSynchronization(__FUNCTION__, true);
//GetGraph()->NotifyGraphChanged();
}
FText UNiagaraNodeConvert::GetNodeTitle(ENodeTitleType::Type TitleType)const
{
if (!AutowireSwizzle.IsEmpty())
{
return FText::FromString(AutowireSwizzle);
}
else if (AutowireMakeType.IsValid())
{
return FText::Format(LOCTEXT("MakeTitle", "Make {0}"), AutowireMakeType.GetNameText());
}
else if (AutowireBreakType.IsValid())
{
return FText::Format(LOCTEXT("BreakTitle", "Break {0}"), AutowireBreakType.GetNameText());
}
else
{
FPinCollectorArray InPins;
FPinCollectorArray OutPins;
GetInputPins(InPins);
GetOutputPins(OutPins);
if (InPins.Num() == 2 && OutPins.Num() == 2)
{
//We are converting one pin type directly to another so we can have a nice name.
const UEdGraphSchema_Niagara* Schema = CastChecked<UEdGraphSchema_Niagara>(GetSchema());
check(Schema);
FNiagaraTypeDefinition AType = Schema->PinToTypeDefinition(InPins[0]);
FNiagaraTypeDefinition BType = Schema->PinToTypeDefinition(OutPins[0]);
return FText::Format(LOCTEXT("SpecificConvertTitle", "{0} -> {1}"), AType.GetNameText(), BType.GetNameText());
}
else
{
return LOCTEXT("DefaultTitle", "Convert");
}
}
}
TArray<FNiagaraConvertConnection>& UNiagaraNodeConvert::GetConnections()
{
return Connections;
}
const TArray<FNiagaraConvertConnection>& UNiagaraNodeConvert::GetConnections() const
{
return Connections;
}
void UNiagaraNodeConvert::OnPinRemoved(UEdGraphPin* PinToRemove)
{
TSet<FGuid> TypePinIds;
for (UEdGraphPin* Pin : GetAllPins())
{
TypePinIds.Add(Pin->PinId);
}
auto RemovePredicate = [&](const FNiagaraConvertConnection& Connection)
{
return TypePinIds.Contains(Connection.SourcePinId) == false || TypePinIds.Contains(Connection.DestinationPinId) == false;
};
Connections.RemoveAll(RemovePredicate);
}
void UNiagaraNodeConvert::InitAsSwizzle(FString Swiz)
{
AutowireSwizzle = Swiz;
}
void UNiagaraNodeConvert::InitAsMake(FNiagaraTypeDefinition Type)
{
AutowireMakeType = Type;
}
void UNiagaraNodeConvert::InitAsBreak(FNiagaraTypeDefinition Type)
{
AutowireBreakType = Type;
}
bool UNiagaraNodeConvert::InitConversion(UEdGraphPin* FromPin, UEdGraphPin* ToPin)
{
const UEdGraphSchema_Niagara* Schema = CastChecked<UEdGraphSchema_Niagara>(GetSchema());
check(Schema);
FNiagaraTypeDefinition FromType = Schema->PinToTypeDefinition(FromPin);
FNiagaraTypeDefinition ToType = Schema->PinToTypeDefinition(ToPin);
//Can only convert normal struct types.
if (!FromType.IsValid() || !ToType.IsValid() || FromType.GetClass() || ToType.GetClass())
{
return false;
}
UEdGraphPin* ConnectFromPin = RequestNewTypedPin(EGPD_Input, FromType);
FromPin->MakeLinkTo(ConnectFromPin);
UEdGraphPin* ConnectToPin = RequestNewTypedPin(EGPD_Output, ToType);
// Before we connect our new link, make sure that the old ones are gone.
ToPin->BreakAllPinLinks();
ToPin->MakeLinkTo(ConnectToPin);
check(ConnectFromPin);
check(ConnectToPin);
TArray<FName> SrcPath;
TArray<FName> DestPath;
TFieldIterator<FProperty> FromPropertyIt(FromType.GetScriptStruct(), EFieldIteratorFlags::IncludeSuper);
TFieldIterator<FProperty> ToPropertyIt(ToType.GetScriptStruct(), EFieldIteratorFlags::IncludeSuper);
TFieldIterator<FProperty> NextFromPropertyIt(FromType.GetScriptStruct(), EFieldIteratorFlags::IncludeSuper);
while (FromPropertyIt && ToPropertyIt)
{
FProperty* FromProp = *FromPropertyIt;
FProperty* ToProp = *ToPropertyIt;
if (NextFromPropertyIt)
{
++NextFromPropertyIt;
}
if (Schema->IsValidNiagaraPropertyType(FromProp) && Schema->IsValidNiagaraPropertyType(ToProp))
{
FNiagaraTypeDefinition FromPropType = Schema->GetTypeDefForProperty(FromProp);
FNiagaraTypeDefinition ToPropType = Schema->GetTypeDefForProperty(ToProp);
SrcPath.Empty();
DestPath.Empty();
if (FNiagaraTypeDefinition::TypesAreAssignable(FromPropType, ToPropType) || FNiagaraTypeDefinition::IsLossyConversion(FromPropType, ToPropType))
{
SrcPath.Add(*FromProp->GetName());
DestPath.Add(*ToProp->GetName());
Connections.Add(FNiagaraConvertConnection(ConnectFromPin->PinId, SrcPath, ConnectToPin->PinId, DestPath));
if (SrcPath.Num())
{
AddExpandedRecord(FNiagaraConvertPinRecord(ConnectFromPin->PinId, SrcPath).GetParent());
}
if (DestPath.Num())
{
AddExpandedRecord(FNiagaraConvertPinRecord(ConnectToPin->PinId, DestPath).GetParent());
}
}
}
//If there is no next From property, just keep with the same one and set it to all future To properties.
if (NextFromPropertyIt)
{
++FromPropertyIt;
}
++ToPropertyIt;
}
return Connections.Num() > 0;
}
bool UNiagaraNodeConvert::IsWiringShown() const
{
return bIsWiringShown;
}
void UNiagaraNodeConvert::SetWiringShown(bool bInShown)
{
bIsWiringShown = bInShown;
VisualsChangedDelegate.Broadcast(this);
}
bool UNiagaraNodeConvert::IsLocalConstantValue() const
{
FPinCollectorArray Outputs;
FPinCollectorArray Inputs;
GetOutputPins(Outputs);
GetInputPins(Inputs);
if (Outputs.Num() == 2 && IsAddPin(Outputs.Last()) && Inputs.Num() >= 2 && IsAddPin(Inputs.Last()))
{
for (UEdGraphPin* InPin : Inputs)
{
if (InPin->LinkedTo.Num() != 0)
{
return false;
}
}
return true;
}
return false;
}
void UNiagaraNodeConvert::RemoveExpandedRecord(const FNiagaraConvertPinRecord& InRecord)
{
if (HasExpandedRecord(InRecord) == true)
{
FScopedTransaction ConnectTransaction(NSLOCTEXT("NiagaraConvert", "ConvertNodeCollpaseTransaction", "Collapse node."));
Modify();
ExpandedItems.Remove(InRecord);
}
}
bool UNiagaraNodeConvert::HasExpandedRecord(const FNiagaraConvertPinRecord& InRecord)
{
for (const FNiagaraConvertPinRecord& ExpandedRecord : ExpandedItems)
{
if (ExpandedRecord.PinId == InRecord.PinId && ExpandedRecord.Path == InRecord.Path)
{
return true;
}
}
return false;
}
void UNiagaraNodeConvert::AddExpandedRecord(const FNiagaraConvertPinRecord& InRecord)
{
if (HasExpandedRecord(InRecord) == false)
{
Modify();
FScopedTransaction ConnectTransaction(NSLOCTEXT("NiagaraConvert", "ConvertNodeExpandedTransaction", "Expand node."));
ExpandedItems.AddUnique(InRecord);
}
}
bool FNiagaraConvertPinRecord::operator ==(const FNiagaraConvertPinRecord & B) const
{
return (PinId == B.PinId && Path == B.Path);
}
#undef LOCTEXT_NAMESPACE