From e7366c046950498d7c7ae665f853c344b6aef6be Mon Sep 17 00:00:00 2001 From: Numblaze Date: Sun, 15 Jun 2025 11:37:52 +0200 Subject: [PATCH 1/5] Flow Node Validation improvements - UFlowNode::ValidateNode() is now blueprint-implementable - UFlowNode now has functions to log validation errors, warnings and notes - UFlowAsset validation fix : if the validation contains only warnings and/or notes but no errors, the messages are still logged --- Source/Flow/Private/FlowAsset.cpp | 18 ++++++++++----- Source/Flow/Private/Nodes/FlowNode.cpp | 31 ++++++++++++++++++++++++++ Source/Flow/Public/Nodes/FlowNode.h | 22 ++++++++++++++++-- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index 8671016c5..bc22aeef9 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -122,10 +122,8 @@ EDataValidationResult UFlowAsset::ValidateAsset(FFlowMessageLog& MessageLog) } Node.Value->ValidationLog.Messages.Empty(); - if (Node.Value->ValidateNode() == EDataValidationResult::Invalid) - { - MessageLog.Messages.Append(Node.Value->ValidationLog.Messages); - } + Node.Value->ValidateNode(); + MessageLog.Messages.Append(Node.Value->ValidationLog.Messages); } else { @@ -134,7 +132,17 @@ EDataValidationResult UFlowAsset::ValidateAsset(FFlowMessageLog& MessageLog) } } - return MessageLog.Messages.Num() > 0 ? EDataValidationResult::Invalid : EDataValidationResult::Valid; + // if at least one error has been has been logged : mark the asset as invalid + for (const TSharedRef& Msg : MessageLog.Messages) + { + if (Msg->GetSeverity() == EMessageSeverity::Error) + { + return EDataValidationResult::Invalid; + } + } + + // otherwise, the asset is considered valid (even with warnings or notes) + return EDataValidationResult::Valid; } bool UFlowAsset::IsNodeOrAddOnClassAllowed(const UClass* FlowNodeOrAddOnClass, FText* OutOptionalFailureReason) const diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index b53c2a9a4..2a9c9a8ba 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -72,8 +72,39 @@ void UFlowNode::PostLoad() FixNode(nullptr); } +EDataValidationResult UFlowNode::ValidateNode() +{ + if (GetClass()->IsFunctionImplementedInScript(GET_FUNCTION_NAME_CHECKED(UFlowNode, K2_ValidateNode))) + { + return K2_ValidateNode(); + } + + return EDataValidationResult::NotValidated; +} + #endif +void UFlowNode::LogValidationError(const FString& Message) +{ +#if WITH_EDITOR + ValidationLog.Error(*Message, this); +#endif +} + +void UFlowNode::LogValidationWarning(const FString& Message) +{ +#if WITH_EDITOR + ValidationLog.Warning(*Message, this); +#endif +} + +void UFlowNode::LogValidationNote(const FString& Message) +{ +#if WITH_EDITOR + ValidationLog.Note(*Message, this); +#endif +} + bool UFlowNode::IsSupportedInputPinName(const FName& PinName) const { const FFlowPin* InputPin = FindFlowPinByName(PinName, InputPins); diff --git a/Source/Flow/Public/Nodes/FlowNode.h b/Source/Flow/Public/Nodes/FlowNode.h index 0857485cb..64687151e 100644 --- a/Source/Flow/Public/Nodes/FlowNode.h +++ b/Source/Flow/Public/Nodes/FlowNode.h @@ -59,10 +59,28 @@ class FLOW_API UFlowNode virtual void PostLoad() override; // -- - virtual EDataValidationResult ValidateNode() { return EDataValidationResult::NotValidated; } - + virtual EDataValidationResult ValidateNode(); #endif + // Flow Node Validation : blueprint compatibility + + // Optional validation override for Blueprints + UFUNCTION(BlueprintImplementableEvent, Category = "FlowNode|Validation", meta = (DisplayName = "Validate Node")) + EDataValidationResult K2_ValidateNode(); + + // Log validation error (editor-only) + UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation") + void LogValidationError(const FString& Message); + + // Log validation warning (editor-only) + UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation") + void LogValidationWarning(const FString& Message); + + // Log validation note (editor-only) + UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation") + void LogValidationNote(const FString& Message); + // -- + // Inherits Guid after graph node UPROPERTY() FGuid NodeGuid; From e9f9476e0e9d087e42a15eaae467a0a548ec6693 Mon Sep 17 00:00:00 2001 From: Numblaze Date: Mon, 16 Jun 2025 07:19:58 +0200 Subject: [PATCH 2/5] Moved validation code from UFlowNode to UFlowNodeBase Also added meta = DevelopmentOnly for blueprint validation functions --- Source/Flow/Private/Nodes/FlowNode.cpp | 31 --------------------- Source/Flow/Private/Nodes/FlowNodeBase.cpp | 32 ++++++++++++++++++++++ Source/Flow/Public/Nodes/FlowNode.h | 21 -------------- Source/Flow/Public/Nodes/FlowNodeBase.h | 21 ++++++++++++++ 4 files changed, 53 insertions(+), 52 deletions(-) diff --git a/Source/Flow/Private/Nodes/FlowNode.cpp b/Source/Flow/Private/Nodes/FlowNode.cpp index 2a9c9a8ba..b53c2a9a4 100644 --- a/Source/Flow/Private/Nodes/FlowNode.cpp +++ b/Source/Flow/Private/Nodes/FlowNode.cpp @@ -72,39 +72,8 @@ void UFlowNode::PostLoad() FixNode(nullptr); } -EDataValidationResult UFlowNode::ValidateNode() -{ - if (GetClass()->IsFunctionImplementedInScript(GET_FUNCTION_NAME_CHECKED(UFlowNode, K2_ValidateNode))) - { - return K2_ValidateNode(); - } - - return EDataValidationResult::NotValidated; -} - #endif -void UFlowNode::LogValidationError(const FString& Message) -{ -#if WITH_EDITOR - ValidationLog.Error(*Message, this); -#endif -} - -void UFlowNode::LogValidationWarning(const FString& Message) -{ -#if WITH_EDITOR - ValidationLog.Warning(*Message, this); -#endif -} - -void UFlowNode::LogValidationNote(const FString& Message) -{ -#if WITH_EDITOR - ValidationLog.Note(*Message, this); -#endif -} - bool UFlowNode::IsSupportedInputPinName(const FName& PinName) const { const FFlowPin* InputPin = FindFlowPinByName(PinName, InputPins); diff --git a/Source/Flow/Private/Nodes/FlowNodeBase.cpp b/Source/Flow/Private/Nodes/FlowNodeBase.cpp index 5d2b15899..0d861dccf 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -263,8 +263,40 @@ FString UFlowNodeBase::GetStatusString() const { return K2_GetStatusString(); } + +EDataValidationResult UFlowNodeBase::ValidateNode() +{ + if (GetClass()->IsFunctionImplementedInScript(GET_FUNCTION_NAME_CHECKED(UFlowNodeBase, K2_ValidateNode))) + { + return K2_ValidateNode(); + } + + return EDataValidationResult::NotValidated; +} + #endif // WITH_EDITOR +void UFlowNodeBase::LogValidationError(const FString& Message) +{ +#if WITH_EDITOR + ValidationLog.Error(*Message, this); +#endif +} + +void UFlowNodeBase::LogValidationWarning(const FString& Message) +{ +#if WITH_EDITOR + ValidationLog.Warning(*Message, this); +#endif +} + +void UFlowNodeBase::LogValidationNote(const FString& Message) +{ +#if WITH_EDITOR + ValidationLog.Note(*Message, this); +#endif +} + UFlowAsset* UFlowNodeBase::GetFlowAsset() const { // In the case of an AddOn, we want our containing FlowNode's Outer, not our own diff --git a/Source/Flow/Public/Nodes/FlowNode.h b/Source/Flow/Public/Nodes/FlowNode.h index 64687151e..14592fe95 100644 --- a/Source/Flow/Public/Nodes/FlowNode.h +++ b/Source/Flow/Public/Nodes/FlowNode.h @@ -58,29 +58,8 @@ class FLOW_API UFlowNode virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; virtual void PostLoad() override; // -- - - virtual EDataValidationResult ValidateNode(); #endif - // Flow Node Validation : blueprint compatibility - - // Optional validation override for Blueprints - UFUNCTION(BlueprintImplementableEvent, Category = "FlowNode|Validation", meta = (DisplayName = "Validate Node")) - EDataValidationResult K2_ValidateNode(); - - // Log validation error (editor-only) - UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation") - void LogValidationError(const FString& Message); - - // Log validation warning (editor-only) - UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation") - void LogValidationWarning(const FString& Message); - - // Log validation note (editor-only) - UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation") - void LogValidationNote(const FString& Message); - // -- - // Inherits Guid after graph node UPROPERTY() FGuid NodeGuid; diff --git a/Source/Flow/Public/Nodes/FlowNodeBase.h b/Source/Flow/Public/Nodes/FlowNodeBase.h index a8578305b..e858242b1 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -314,6 +314,8 @@ class FLOW_API UFlowNodeBase // Called by owning FlowNode to add to its Status String. // (may be multi-line) virtual FString GetStatusString() const; + + virtual EDataValidationResult ValidateNode(); #endif // WITH_EDITOR protected: @@ -321,6 +323,25 @@ class FLOW_API UFlowNodeBase UFUNCTION(BlueprintImplementableEvent, Category = "FlowNode", meta = (DisplayName = "Get Status String")) FString K2_GetStatusString() const; + // Flow Node Validation : blueprint compatibility + + // Optional validation override for Blueprints + UFUNCTION(BlueprintImplementableEvent, Category = "FlowNode|Validation", meta = (DisplayName = "Validate Node", DevelopmentOnly)) + EDataValidationResult K2_ValidateNode(); + + // Log validation error (editor-only) + UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation", meta = (DevelopmentOnly)) + void LogValidationError(const FString& Message); + + // Log validation warning (editor-only) + UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation", meta = (DevelopmentOnly)) + void LogValidationWarning(const FString& Message); + + // Log validation note (editor-only) + UFUNCTION(BlueprintCallable, Category = "FlowNode|Validation", meta = (DevelopmentOnly)) + void LogValidationNote(const FString& Message); + // -- + #if WITH_EDITORONLY_DATA protected: UPROPERTY() From 39f60ec2d605f8502834142b22eff476fc4427ac Mon Sep 17 00:00:00 2001 From: Numblaze Date: Mon, 16 Jun 2025 08:17:08 +0200 Subject: [PATCH 3/5] Flow Node AddOns validation handled in UFlowAsset::ValidateAsset --- Source/Flow/Private/FlowAsset.cpp | 45 +++++++++++++++++++++++++++++++ Source/Flow/Public/FlowAsset.h | 6 +++++ 2 files changed, 51 insertions(+) diff --git a/Source/Flow/Private/FlowAsset.cpp b/Source/Flow/Private/FlowAsset.cpp index bc22aeef9..481acb29e 100644 --- a/Source/Flow/Private/FlowAsset.cpp +++ b/Source/Flow/Private/FlowAsset.cpp @@ -23,7 +23,9 @@ #include "Editor/EditorEngine.h" FString UFlowAsset::ValidationError_NodeClassNotAllowed = TEXT("Node class {0} is not allowed in this asset."); +FString UFlowAsset::ValidationError_AddOnNodeClassNotAllowed = TEXT("AddOn Node class {0} is not allowed in this asset."); FString UFlowAsset::ValidationError_NullNodeInstance = TEXT("Node with GUID {0} is NULL"); +FString UFlowAsset::ValidationError_NullAddOnNodeInstance = TEXT("Node with GUID {0} has NULL AddOn(s)"); #endif #include UE_INLINE_GENERATED_CPP_BY_NAME(FlowAsset) @@ -124,6 +126,20 @@ EDataValidationResult UFlowAsset::ValidateAsset(FFlowMessageLog& MessageLog) Node.Value->ValidationLog.Messages.Empty(); Node.Value->ValidateNode(); MessageLog.Messages.Append(Node.Value->ValidationLog.Messages); + + // Validate AddOns + for (UFlowNodeAddOn* AddOn : Node.Value->GetFlowNodeAddOnChildren()) + { + if (IsValid(AddOn)) + { + ValidateAddOnTree(*AddOn, MessageLog); + } + else + { + const FString ErrorMsg = FString::Format(*ValidationError_NullAddOnNodeInstance, { *Node.Key.ToString() }); + MessageLog.Error(*ErrorMsg, this); + } + } } else { @@ -247,6 +263,35 @@ bool UFlowAsset::IsFlowNodeClassInDeniedClasses(const UClass& FlowNodeClass) con return false; } +void UFlowAsset::ValidateAddOnTree(UFlowNodeAddOn& AddOn, FFlowMessageLog& MessageLog) +{ + // Filter unauthorized addon nodes + FText FailureReason; + if (!IsNodeOrAddOnClassAllowed(AddOn.GetClass(), &FailureReason)) + { + const FString ErrorMsg = + FailureReason.IsEmpty() + ? FString::Format(*ValidationError_AddOnNodeClassNotAllowed, { *AddOn.GetClass()->GetName() }) + : FailureReason.ToString(); + + MessageLog.Error(*ErrorMsg, AddOn.GetFlowNodeSelfOrOwner()); + } + + // Validate AddOn + AddOn.ValidationLog.Messages.Empty(); + AddOn.ValidateNode(); + MessageLog.Messages.Append(AddOn.ValidationLog.Messages); + + // Validate Children + for (UFlowNodeAddOn* Child : AddOn.GetFlowNodeAddOnChildren()) + { + if (IsValid(Child)) + { + ValidateAddOnTree(*Child, MessageLog); + } + } +} + bool UFlowAsset::IsFlowNodeClassInAllowedClasses(const UClass& FlowNodeClass, const TSubclassOf& RequiredAncestor) const { diff --git a/Source/Flow/Public/FlowAsset.h b/Source/Flow/Public/FlowAsset.h index 0e2963463..d04358ff2 100644 --- a/Source/Flow/Public/FlowAsset.h +++ b/Source/Flow/Public/FlowAsset.h @@ -103,7 +103,9 @@ class FLOW_API UFlowAsset : public UObject FSimpleDelegate OnDetailsRefreshRequested; static FString ValidationError_NodeClassNotAllowed; + static FString ValidationError_AddOnNodeClassNotAllowed; static FString ValidationError_NullNodeInstance; + static FString ValidationError_NullAddOnNodeInstance; private: UPROPERTY() @@ -126,6 +128,10 @@ class FLOW_API UFlowAsset : public UObject bool IsFlowNodeClassInAllowedClasses(const UClass& FlowNodeClass, const TSubclassOf& RequiredAncestor = nullptr) const; bool IsFlowNodeClassInDeniedClasses(const UClass& FlowNodeClass) const; + +private: + // Recursively validates the given addon and its children. + void ValidateAddOnTree(UFlowNodeAddOn& AddOn, FFlowMessageLog& MessageLog); #endif ////////////////////////////////////////////////////////////////////////// From 50ea099c536089e9646c4b4f0f2117bbc9991130 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysiek=20Justy=C5=84ski?= Date: Wed, 20 Aug 2025 19:24:20 +0200 Subject: [PATCH 4/5] Update FlowNodeBase.cpp --- Source/Flow/Private/Nodes/FlowNodeBase.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/Flow/Private/Nodes/FlowNodeBase.cpp b/Source/Flow/Private/Nodes/FlowNodeBase.cpp index dc361be61..698306d94 100644 --- a/Source/Flow/Private/Nodes/FlowNodeBase.cpp +++ b/Source/Flow/Private/Nodes/FlowNodeBase.cpp @@ -260,11 +260,6 @@ TArray UFlowNodeBase::GetContextOutputs() const return ContextOutputs; } -FString UFlowNodeBase::GetStatusString() const -{ - return K2_GetStatusString(); -} - EDataValidationResult UFlowNodeBase::ValidateNode() { if (GetClass()->IsFunctionImplementedInScript(GET_FUNCTION_NAME_CHECKED(UFlowNodeBase, K2_ValidateNode))) @@ -275,6 +270,11 @@ EDataValidationResult UFlowNodeBase::ValidateNode() return EDataValidationResult::NotValidated; } +FString UFlowNodeBase::GetStatusString() const +{ + return K2_GetStatusString(); +} + #endif // WITH_EDITOR void UFlowNodeBase::LogValidationError(const FString& Message) From 14f030e74e73ac920d75d2088a358cdcab08e604 Mon Sep 17 00:00:00 2001 From: Numblaze Date: Mon, 6 Oct 2025 19:00:31 +0200 Subject: [PATCH 5/5] Added parent node support for Flow Node AddOns ++ UFlowNodeBase::GetParentNode ++ UFlowNodeAddon property ParentNode (editor only) with getter (overiding UFlowNodeBase::GetParentNode) and setter ++ UFlowNode returns self for GetParentNode ++ UFlowGraphNode::SetParentNodeForSubNode attempts to set the parent node to the flow node instance if it is an addOn - UFlowGraphNode::AddSubNode SubNode->SetParentNodeForSubNode is executed after SubNode->PostPlacedNewNode() to ensure the instance is set for parent detection - Updated FFlowGraphToken::FFlowGraphToken to use the new getter of the parent node --- Source/Flow/Private/FlowMessageLog.cpp | 6 ++++- Source/Flow/Public/AddOns/FlowNodeAddOn.h | 23 +++++++++++++++++ Source/Flow/Public/Nodes/FlowNode.h | 5 ++++ Source/Flow/Public/Nodes/FlowNodeBase.h | 3 +++ .../Private/Graph/Nodes/FlowGraphNode.cpp | 25 ++++++++++++++++++- 5 files changed, 60 insertions(+), 2 deletions(-) diff --git a/Source/Flow/Private/FlowMessageLog.cpp b/Source/Flow/Private/FlowMessageLog.cpp index 39c1d2997..0976a0e6d 100644 --- a/Source/Flow/Private/FlowMessageLog.cpp +++ b/Source/Flow/Private/FlowMessageLog.cpp @@ -16,7 +16,11 @@ FFlowGraphToken::FFlowGraphToken(const UFlowAsset* InFlowAsset) } FFlowGraphToken::FFlowGraphToken(const UFlowNodeBase* InFlowNodeBase) - : GraphNode(InFlowNodeBase->GetGraphNode()) + : GraphNode( + InFlowNodeBase->GetParentNode() + ? InFlowNodeBase->GetParentNode()->GetGraphNode() + : nullptr + ) { CachedText = InFlowNodeBase->GetNodeTitle(); } diff --git a/Source/Flow/Public/AddOns/FlowNodeAddOn.h b/Source/Flow/Public/AddOns/FlowNodeAddOn.h index 47fb5ab4c..b8dc8470c 100644 --- a/Source/Flow/Public/AddOns/FlowNodeAddOn.h +++ b/Source/Flow/Public/AddOns/FlowNodeAddOn.h @@ -22,6 +22,12 @@ class UFlowNodeAddOn : public UFlowNodeBase // (accessible only when initialized, runtime only) UPROPERTY(Transient) TObjectPtr FlowNode; + +#if WITH_EDITORONLY_DATA + // Editor-only pointer to the owning top-level UFlowNode + UPROPERTY(Transient) + TObjectPtr ParentNode; +#endif // Input pins to add to the owning flow node // If defined, ExecuteInput will only be executed for these inputs @@ -78,6 +84,23 @@ class UFlowNodeAddOn : public UFlowNodeBase // by default, uses the seed for the Flow Node that this addon is attached to. FLOW_API virtual int32 GetRandomSeed() const override; + // Sets the parent node. Editor only. + FLOW_API virtual void SetParentNode(UFlowNode* InParent) + { +#if WITH_EDITORONLY_DATA + ParentNode = InParent; +#endif // WITH_EDITOR + } + + // Editor only. + FLOW_API virtual const UFlowNode* GetParentNode() const override + { +#if WITH_EDITORONLY_DATA + if (ParentNode) return ParentNode; +#endif // WITH_EDITOR + return UFlowNodeBase::GetFlowNodeSelfOrOwner(); + } + #if WITH_EDITOR // IFlowContextPinSupplierInterface FLOW_API virtual bool SupportsContextPins() const override { return Super::SupportsContextPins() || (!InputPins.IsEmpty() || !OutputPins.IsEmpty()); } diff --git a/Source/Flow/Public/Nodes/FlowNode.h b/Source/Flow/Public/Nodes/FlowNode.h index b058c0457..80a2b9271 100644 --- a/Source/Flow/Public/Nodes/FlowNode.h +++ b/Source/Flow/Public/Nodes/FlowNode.h @@ -75,6 +75,11 @@ class FLOW_API UFlowNode // by default based on the node Guid, // but may be overridden in subclasses to supply some other value. virtual int32 GetRandomSeed() const override { return GetTypeHash(NodeGuid); } + + virtual const UFlowNode* GetParentNode() const override + { + return UFlowNodeBase::GetFlowNodeSelfOrOwner(); + } public: virtual bool CanFinishGraph() const { return false; } diff --git a/Source/Flow/Public/Nodes/FlowNodeBase.h b/Source/Flow/Public/Nodes/FlowNodeBase.h index 567fe76a7..1fa511acc 100644 --- a/Source/Flow/Public/Nodes/FlowNodeBase.h +++ b/Source/Flow/Public/Nodes/FlowNodeBase.h @@ -118,6 +118,9 @@ class FLOW_API UFlowNodeBase UFUNCTION(BlueprintPure, Category = "FlowNode") virtual int32 GetRandomSeed() const PURE_VIRTUAL(GetRandomSeed, return 0;); + // Returns the owning top-level Flow node. + virtual const UFlowNode* GetParentNode() const PURE_VIRTUAL(GetParentNode, return nullptr;); + ////////////////////////////////////////////////////////////////////////// // Pins diff --git a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp index 50056584d..15dab3c60 100644 --- a/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp +++ b/Source/FlowEditor/Private/Graph/Nodes/FlowGraphNode.cpp @@ -1320,6 +1320,27 @@ void UFlowGraphNode::SetParentNodeForSubNode(UFlowGraphNode* InParentNode) { // Once a SubNode, always a SubNode bIsSubNode = true; + +#if WITH_EDITOR + // Attempt to set the parent node if the instance is an AddOn + if (UFlowNodeAddOn* SelfAsAddOn = Cast(NodeInstance)) + { + const UFlowNode* TopLevelOwner = nullptr; + + if (const UFlowNode* ParentFlowNode = Cast(InParentNode->NodeInstance)) + { + // Parent is the top-level flow node + TopLevelOwner = ParentFlowNode; + } + else if (const UFlowNodeAddOn* ParentAddOn = Cast(InParentNode->NodeInstance)) + { + // Bubble up to the top-level flow node + TopLevelOwner = ParentAddOn->GetParentNode(); + } + + SelfAsAddOn->SetParentNode(const_cast(TopLevelOwner)); + } +#endif } ParentNode = InParentNode; @@ -1479,10 +1500,12 @@ void UFlowGraphNode::AddSubNode(UFlowGraphNode* SubNode, class UEdGraph* ParentG // set outer to be the graph so it doesn't go away SubNode->Rename(nullptr, ParentGraph, REN_NonTransactional); - SubNode->SetParentNodeForSubNode(this); SubNode->CreateNewGuid(); SubNode->PostPlacedNewNode(); + + SubNode->SetParentNodeForSubNode(this); + SubNode->AllocateDefaultPins(); SubNode->AutowireNewNode(nullptr);