// 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& InConnections, TArray& OutMissingConnections, int32 ConnectionDepth = 0) { TArray 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 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 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(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 UNiagaraNodeConvert::CreateVisualWidget() { return SNew(SNiagaraGraphNodeConvert, this); } void UNiagaraNodeConvert::Compile(FTranslator* Translator, TArray& CompileOutputs) const { FPinCollectorArray InputPins; GetCompilationInputPins(InputPins); TArray> 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(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 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 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(Struct); for (FProperty* Property : TFieldRange(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& 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(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(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(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 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(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 SrcPath; TArray DestPath; //Add a corresponding pin for each property in the from Pin. for (TFieldIterator 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 SrcPath; TArray 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(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& UNiagaraNodeConvert::GetConnections() { return Connections; } const TArray& UNiagaraNodeConvert::GetConnections() const { return Connections; } void UNiagaraNodeConvert::OnPinRemoved(UEdGraphPin* PinToRemove) { TSet 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(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 SrcPath; TArray DestPath; TFieldIterator FromPropertyIt(FromType.GetScriptStruct(), EFieldIteratorFlags::IncludeSuper); TFieldIterator ToPropertyIt(ToType.GetScriptStruct(), EFieldIteratorFlags::IncludeSuper); TFieldIterator 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