861 lines
27 KiB
C++
861 lines
27 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
|
|
#include "EdGraphUtilities.h"
|
|
|
|
#include "BlueprintUtilities.h"
|
|
#include "Containers/EnumAsByte.h"
|
|
#include "CoreGlobals.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraph/EdGraphNode.h"
|
|
#include "EdGraph/EdGraphSchema.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "Exporters/Exporter.h"
|
|
#include "Factories.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "HAL/PlatformCrt.h"
|
|
#include "Internationalization/Internationalization.h"
|
|
#include "Internationalization/Text.h"
|
|
#include "K2Node_EditablePinBase.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Kismet2/CompilerResultsLog.h"
|
|
#include "Math/IntRect.h"
|
|
#include "Misc/AssertionMacros.h"
|
|
#include "Stats/Stats.h"
|
|
#include "Styling/CoreStyle.h"
|
|
#include "Styling/ISlateStyle.h"
|
|
#include "Templates/Casts.h"
|
|
#include "UObject/Class.h"
|
|
#include "UObject/Object.h"
|
|
#include "UObject/ObjectPtr.h"
|
|
#include "UObject/Package.h"
|
|
#include "UObject/PropertyPortFlags.h"
|
|
#include "UObject/UObjectGlobals.h"
|
|
#include "UObject/UObjectMarks.h"
|
|
#include "UnrealExporter.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Clone Graph"), EKismetCompilerStats_CloneGraph, STATGROUP_KismetCompiler);
|
|
DECLARE_CYCLE_STAT(TEXT("Clone Graph - Post Process"), EKismetCompilerStats_CloneGraph_PostProcess, STATGROUP_KismetCompiler);
|
|
|
|
/////////////////////////////////////////////////////
|
|
// Local namespace
|
|
|
|
namespace
|
|
{
|
|
// Reconcile other pin links:
|
|
// - Links between nodes within the copied set are fine
|
|
// - Links to nodes that were not copied need to be fixed up if the copy-paste was in the same graph or broken completely
|
|
void PostProcessPastedNodePinLinks(TSet<UEdGraphNode*>& InNodes)
|
|
{
|
|
for (TSet<UEdGraphNode*>::TIterator It(InNodes); It; ++It)
|
|
{
|
|
UEdGraphNode* Node = *It;
|
|
UEdGraph* CurrentGraph = Node->GetGraph();
|
|
|
|
for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
|
|
{
|
|
UEdGraphPin* ThisPin = Node->Pins[PinIndex];
|
|
|
|
// Ensure on any NULL entry, as it means there was a problem importing the pin from text, and we should be alerted to that.
|
|
if (ensure(ThisPin))
|
|
{
|
|
for (int32 LinkIndex = 0; LinkIndex < ThisPin->LinkedTo.Num(); )
|
|
{
|
|
UEdGraphPin* OtherPin = ThisPin->LinkedTo[LinkIndex];
|
|
|
|
if (OtherPin == nullptr)
|
|
{
|
|
// Totally bogus link
|
|
ThisPin->LinkedTo.RemoveAtSwap(LinkIndex);
|
|
}
|
|
else if (!InNodes.Contains(OtherPin->GetOwningNode()))
|
|
{
|
|
// It's a link across the selection set, so it should be broken
|
|
OtherPin->LinkedTo.RemoveSwap(ThisPin);
|
|
ThisPin->LinkedTo.RemoveAtSwap(LinkIndex);
|
|
}
|
|
else if (!OtherPin->LinkedTo.Contains(ThisPin))
|
|
{
|
|
// The link needs to be reciprocal
|
|
check(OtherPin->GetOwningNode()->GetGraph() == CurrentGraph);
|
|
OtherPin->LinkedTo.Add(ThisPin);
|
|
++LinkIndex;
|
|
}
|
|
else
|
|
{
|
|
// Everything seems fine but sanity check the graph
|
|
check(OtherPin->GetOwningNode()->GetGraph() == CurrentGraph);
|
|
++LinkIndex;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Remove NULL entries; these will be replaced with a default value when the node is reconstructed below.
|
|
Node->Pins.RemoveAt(PinIndex--);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FGraphObjectTextFactory
|
|
|
|
/** Helper class used to paste a buffer of text and create nodes and pins from it */
|
|
struct FGraphObjectTextFactory : public FCustomizableTextObjectFactory
|
|
{
|
|
public:
|
|
TSet<UEdGraphNode*> SpawnedNodes;
|
|
TSet<UEdGraphNode*> SubstituteNodes;
|
|
const UEdGraph* DestinationGraph;
|
|
TSet<FName> ExtraNamesInUse;
|
|
TSet<UEdGraphNode*> NodesToDestroy;
|
|
public:
|
|
FGraphObjectTextFactory(const UEdGraph* InDestinationGraph)
|
|
: FCustomizableTextObjectFactory(GWarn)
|
|
, DestinationGraph(InDestinationGraph)
|
|
{
|
|
}
|
|
|
|
protected:
|
|
virtual bool CanCreateClass(UClass* ObjectClass, bool& bOmitSubObjs) const override
|
|
{
|
|
if (const UEdGraphNode* DefaultNode = Cast<UEdGraphNode>(ObjectClass->GetDefaultObject()))
|
|
{
|
|
// if the root node can't be created, don't continue to check sub-
|
|
// objects (for like collapsed graphs, or anim state-machine nodes)
|
|
bOmitSubObjs = true;
|
|
|
|
if (DefaultNode->CanDuplicateNode())
|
|
{
|
|
if (DestinationGraph != NULL)
|
|
{
|
|
if (DefaultNode->CanCreateUnderSpecifiedSchema(DestinationGraph->GetSchema()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
virtual void ProcessConstructedObject(UObject* CreatedObject) override
|
|
{
|
|
if (UEdGraphNode* Node = Cast<UEdGraphNode>(CreatedObject))
|
|
{
|
|
UEdGraphNode* CreatedNode = Node;
|
|
if (!Node->CanPasteHere(DestinationGraph))
|
|
{
|
|
// Attempt to create a substitute node if it cannot be pasted (note: the return value can be NULL, indicating that the node cannot be pasted into the graph)
|
|
CreatedNode = DestinationGraph->GetSchema()->CreateSubstituteNode(CreatedNode, DestinationGraph, &InstanceGraph, ExtraNamesInUse);
|
|
SubstituteNodes.Add(CreatedNode);
|
|
}
|
|
|
|
if (Node != CreatedNode)
|
|
{
|
|
NodesToDestroy.Add(Node);
|
|
}
|
|
|
|
if (CreatedNode)
|
|
{
|
|
SpawnedNodes.Add(CreatedNode);
|
|
|
|
CreatedNode->GetGraph()->Nodes.Add(CreatedNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
virtual void PostProcessConstructedObjects() override
|
|
{
|
|
if (SubstituteNodes.Num() > 0)
|
|
{
|
|
FText NotificationText;
|
|
if (SubstituteNodes.Contains(nullptr))
|
|
{
|
|
if (SubstituteNodes.Num() > 1)
|
|
{
|
|
NotificationText = NSLOCTEXT("EdGraphUtilities", "SubstituteAndSkippedNodesWarning", "One or more copied nodes were substituted and/or could not be pasted into this graph!");
|
|
}
|
|
else
|
|
{
|
|
NotificationText = NSLOCTEXT("EdGraphUtilities", "SkippedNodesWarning", "One or more copied nodes could not be pasted into this graph!");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NotificationText = NSLOCTEXT("EdGraphUtilities", "SubstituteNodesWarning", "One or more copied nodes were substituted during paste!");
|
|
}
|
|
|
|
// Display a notification to inform the user that one or more nodes were substituted rather than pasted into the new graph.
|
|
FNotificationInfo Info(NotificationText);
|
|
Info.ExpireDuration = 3.0f;
|
|
Info.bUseLargeFont = false;
|
|
Info.Image = FCoreStyle::Get().GetBrush(TEXT("MessageLog.Warning"));
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if (Notification.IsValid())
|
|
{
|
|
Notification->SetCompletionState(SNotificationItem::CS_None);
|
|
}
|
|
}
|
|
|
|
// Fix up pin cross-links, etc. before removing incompatible nodes below. Otherwise, BreakAllNodeLinks() will complain about non-reciprocating pin links.
|
|
PostProcessPastedNodePinLinks(NodesToDestroy);
|
|
|
|
for (UEdGraphNode* Node : NodesToDestroy)
|
|
{
|
|
// Move the old node into the transient package so that it is GC'd
|
|
Node->BreakAllNodeLinks();
|
|
Node->Rename(NULL, GetTransientPackage());
|
|
Node->MarkAsGarbage();
|
|
}
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FEdGraphUtilities
|
|
|
|
TArray< TSharedPtr<FGraphPanelNodeFactory> > FEdGraphUtilities::VisualNodeFactories;
|
|
TArray< TSharedPtr<FGraphPanelPinFactory> > FEdGraphUtilities::VisualPinFactories;
|
|
TArray< TSharedPtr<FGraphPanelPinConnectionFactory> > FEdGraphUtilities::VisualPinConnectionFactories;
|
|
|
|
// Call PostPasteNode on each node
|
|
void FEdGraphUtilities::PostProcessPastedNodes(TSet<UEdGraphNode*>& SpawnedNodes)
|
|
{
|
|
// Run thru and fix up the node's pin links; they may point to invalid pins if the paste was to another graph
|
|
PostProcessPastedNodePinLinks(SpawnedNodes);
|
|
|
|
// Give every node a chance to deep copy associated resources, etc...
|
|
for (TSet<UEdGraphNode*>::TIterator It(SpawnedNodes); It; ++It)
|
|
{
|
|
UEdGraphNode* Node = *It;
|
|
|
|
Node->PostPasteNode();
|
|
Node->ReconstructNode();
|
|
|
|
// Ensure we have RF_Transactional set on all pasted nodes, as its not copied in the T3D format
|
|
Node->SetFlags(RF_Transactional);
|
|
}
|
|
}
|
|
|
|
UEdGraphPin* FEdGraphUtilities::GetNetFromPin(UEdGraphPin* Pin)
|
|
{
|
|
if ((Pin->Direction == EGPD_Input) && (Pin->LinkedTo.Num() > 0))
|
|
{
|
|
return Pin->LinkedTo[0];
|
|
}
|
|
else
|
|
{
|
|
return Pin;
|
|
}
|
|
}
|
|
|
|
// Clones (deep copies) a UEdGraph, including all of it's nodes and pins and their links,
|
|
// maintaining a mapping from the clone to the source nodes (even across multiple clonings)
|
|
UEdGraph* FEdGraphUtilities::CloneGraph(UEdGraph* InSource, UObject* NewOuter, FCompilerResultsLog* MessageLog, bool bCloningForCompile)
|
|
{
|
|
BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_CloneGraph);
|
|
|
|
// Duplicate the graph, keeping track of what was duplicated
|
|
TMap<UObject*, UObject*> DuplicatedObjectList;
|
|
|
|
UObject* UseOuter = (NewOuter != nullptr) ? NewOuter : GetTransientPackage();
|
|
FObjectDuplicationParameters Parameters(InSource, UseOuter);
|
|
Parameters.CreatedObjects = &DuplicatedObjectList;
|
|
|
|
if (bCloningForCompile || (NewOuter == nullptr))
|
|
{
|
|
Parameters.ApplyFlags |= RF_Transient;
|
|
Parameters.FlagMask &= ~RF_Transactional;
|
|
}
|
|
|
|
UEdGraph* ClonedGraph = CastChecked<UEdGraph>(StaticDuplicateObjectEx(Parameters));
|
|
|
|
// During compilation, do not clone disabled (e.g. ghost) nodes from non-transient source graphs.
|
|
const bool bExcludeDisabledNodes = bCloningForCompile && InSource && !InSource->HasAnyFlags(RF_Transient);
|
|
|
|
// Exclude disabled nodes and/or store backtrack links from each duplicated object to the original source object.
|
|
if (bExcludeDisabledNodes || MessageLog != nullptr)
|
|
{
|
|
BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_CloneGraph_PostProcess);
|
|
|
|
for (TMap<UObject*, UObject*>::TIterator It(DuplicatedObjectList); It; ++It)
|
|
{
|
|
bool bIsExcludedNode = false;
|
|
|
|
UObject* const Source = It.Key();
|
|
UObject* const Dest = It.Value();
|
|
|
|
UEdGraphNode* SrcNode = Cast<UEdGraphNode>(Source);
|
|
UEdGraphNode* DstNode = Cast<UEdGraphNode>(Dest);
|
|
if (SrcNode && DstNode)
|
|
{
|
|
// Determine whether the source is a disabled node that should be excluded from the cloned graph.
|
|
bIsExcludedNode = bExcludeDisabledNodes && !SrcNode->IsNodeEnabled();
|
|
|
|
// associate pins, no known case of StaticDuplicateObjectEx resulting in a different number of pins, but
|
|
// if that does happen we just associate as many pins as we can:
|
|
ensure(SrcNode->Pins.Num() == DstNode->Pins.Num());
|
|
for (int32 PinIdx = 0; PinIdx < SrcNode->Pins.Num() && PinIdx < DstNode->Pins.Num(); ++PinIdx)
|
|
{
|
|
UEdGraphPin* SrcPin = SrcNode->Pins[PinIdx];
|
|
UEdGraphPin* DstPin = DstNode->Pins[PinIdx];
|
|
if (ensure(SrcPin && DstPin))
|
|
{
|
|
// Don't record pins to the backtrack map if this is an excluded node.
|
|
if (bIsExcludedNode)
|
|
{
|
|
// Patch any input links to pass through to the other side.
|
|
if (DstPin->Direction == EGPD_Input && DstPin->LinkedTo.Num() > 0)
|
|
{
|
|
UEdGraphPin* PassThroughPin = DstNode->GetPassThroughPin(DstPin);
|
|
if (PassThroughPin != nullptr && PassThroughPin->LinkedTo.Num() > 0)
|
|
{
|
|
for (UEdGraphPin* OutputPin : DstPin->LinkedTo)
|
|
{
|
|
for (UEdGraphPin* InputPin : PassThroughPin->LinkedTo)
|
|
{
|
|
InputPin->LinkedTo.Add(OutputPin);
|
|
OutputPin->LinkedTo.Add(InputPin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else if (MessageLog)
|
|
{
|
|
MessageLog->NotifyIntermediatePinCreation(DstPin, SrcPin);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bIsExcludedNode)
|
|
{
|
|
// Break remaining node links, if any exist.
|
|
DstNode->BreakAllNodeLinks();
|
|
|
|
// Remove the node from the cloned graph, if valid.
|
|
if (ClonedGraph)
|
|
{
|
|
ClonedGraph->Nodes.Remove(DstNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Don't record excluded nodes to the backtrack map.
|
|
if (MessageLog && !bIsExcludedNode)
|
|
{
|
|
MessageLog->NotifyIntermediateObjectCreation(Dest, Source);
|
|
}
|
|
}
|
|
}
|
|
|
|
return ClonedGraph;
|
|
}
|
|
|
|
// Clones the content from SourceGraph and merges it into MergeTarget; including merging/flattening all of the children from the SourceGraph into MergeTarget
|
|
void FEdGraphUtilities::CloneAndMergeGraphIn(UEdGraph* MergeTarget, UEdGraph* SourceGraph, FCompilerResultsLog& MessageLog, bool bRequireSchemaMatch, bool bInIsCompiling/* = false*/, TArray<UEdGraphNode*>* OutClonedNodes)
|
|
{
|
|
// Clone the graph, then move all of it's children
|
|
UEdGraph* ClonedGraph = CloneGraph(SourceGraph, NULL, &MessageLog, true);
|
|
MergeChildrenGraphsIn(ClonedGraph, ClonedGraph, bRequireSchemaMatch, false, &MessageLog);
|
|
|
|
// Duplicate the list of cloned nodes
|
|
if (OutClonedNodes != NULL)
|
|
{
|
|
OutClonedNodes->Append(ClonedGraph->Nodes);
|
|
}
|
|
|
|
// Determine if we are regenerating a blueprint on load
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(MergeTarget);
|
|
const bool bIsLoading = Blueprint ? Blueprint->bIsRegeneratingOnLoad : false;
|
|
|
|
// Move them all to the destination
|
|
ClonedGraph->MoveNodesToAnotherGraph(MergeTarget, IsAsyncLoading() || bIsLoading, bInIsCompiling);
|
|
}
|
|
|
|
// Moves the contents of all of the children graphs (recursively) into the target graph. This does not clone, it's destructive to the source
|
|
void FEdGraphUtilities::MergeChildrenGraphsIn(UEdGraph* MergeTarget, UEdGraph* ParentGraph, bool bRequireSchemaMatch, bool bInIsCompiling/* = false*/, FCompilerResultsLog* MessageLog/* = nullptr*/)
|
|
{
|
|
// Determine if we are regenerating a blueprint on load
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(MergeTarget);
|
|
const bool bIsLoading = Blueprint ? Blueprint->bIsRegeneratingOnLoad : false;
|
|
|
|
// Merge all children nodes in too
|
|
for (int32 Index = 0; Index < ParentGraph->SubGraphs.Num(); ++Index)
|
|
{
|
|
UEdGraph* ChildGraph = ParentGraph->SubGraphs[Index];
|
|
|
|
auto NodeOwner = Cast<const UEdGraphNode>(ChildGraph ? ChildGraph->GetOuter() : nullptr);
|
|
const bool bNonVirtualGraph = NodeOwner ? NodeOwner->ShouldMergeChildGraphs() : true;
|
|
|
|
// Only merge children in with the same schema as the parent
|
|
auto ChildSchema = ChildGraph ? ChildGraph->GetSchema() : nullptr;
|
|
auto TargetSchema = MergeTarget ? MergeTarget->GetSchema() : nullptr;
|
|
const bool bSchemaMatches = ChildSchema && TargetSchema && ChildSchema->GetClass()->IsChildOf(TargetSchema->GetClass());
|
|
const bool bDoMerge = bNonVirtualGraph && (!bRequireSchemaMatch || bSchemaMatches);
|
|
if (bDoMerge)
|
|
{
|
|
// Even if we don't require a match to recurse, we do to actually copy the nodes
|
|
if (bSchemaMatches)
|
|
{
|
|
ChildGraph->MoveNodesToAnotherGraph(MergeTarget, IsAsyncLoading() || bIsLoading, bInIsCompiling);
|
|
}
|
|
else if (bRequireSchemaMatch && NodeOwner && MessageLog)
|
|
{
|
|
// If merging requires a schema match and we have a valid owner for the child graph, throw an error message on the outer node context.
|
|
MessageLog->Error(*NSLOCTEXT("EdGraphUtilities", "CannotMergeChildGraph_Error", "Unable to merge child graph for node @@. The child graph's schema is incompatible with the outer graph.").ToString(), MessageLog->FindSourceObject(NodeOwner));
|
|
}
|
|
|
|
MergeChildrenGraphsIn(MergeTarget, ChildGraph, bRequireSchemaMatch, bInIsCompiling, MessageLog);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Tries to rename the graph to have a name similar to BaseName
|
|
void FEdGraphUtilities::RenameGraphCloseToName(UEdGraph* Graph, const FString& BaseName, int32 StartIndex)
|
|
{
|
|
FString NewName = BaseName;
|
|
|
|
int32 NameIndex = StartIndex;
|
|
for (;;)
|
|
{
|
|
if (Graph->Rename(*NewName, Graph->GetOuter(), REN_Test))
|
|
{
|
|
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph);
|
|
Graph->Rename(*NewName, Graph->GetOuter(), REN_DontCreateRedirectors);
|
|
return;
|
|
}
|
|
|
|
NewName = FString::Printf(TEXT("%s_%d"), *BaseName, NameIndex);
|
|
++NameIndex;
|
|
}
|
|
}
|
|
|
|
void FEdGraphUtilities::RenameGraphToNameOrCloseToName(UEdGraph* Graph, const FString& DesiredName)
|
|
{
|
|
if (Graph->Rename(*DesiredName, Graph->GetOuter(), REN_Test))
|
|
{
|
|
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph);
|
|
Graph->Rename(*DesiredName, Graph->GetOuter(), REN_DontCreateRedirectors);
|
|
}
|
|
else
|
|
{
|
|
RenameGraphCloseToName(Graph, DesiredName);
|
|
}
|
|
}
|
|
|
|
// Exports a set of nodes to text
|
|
void FEdGraphUtilities::ExportNodesToText(TSet<UObject*> NodesToExport, /*out*/ FString& ExportedText)
|
|
{
|
|
// Clear the mark state for saving.
|
|
UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp));
|
|
|
|
FStringOutputDevice Archive;
|
|
const FExportObjectInnerContext Context;
|
|
|
|
// Export each of the selected nodes
|
|
UObject* LastOuter = NULL;
|
|
for (TSet<UObject*>::TConstIterator NodeIt(NodesToExport); NodeIt; ++NodeIt)
|
|
{
|
|
UObject* Node = *NodeIt;
|
|
|
|
// The nodes should all be from the same scope
|
|
UObject* ThisOuter = Node->GetOuter();
|
|
check((LastOuter == ThisOuter) || (LastOuter == NULL));
|
|
LastOuter = ThisOuter;
|
|
|
|
UExporter::ExportToOutputDevice(&Context, Node, NULL, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified|PPF_Copy|PPF_Delimited, false, ThisOuter);
|
|
}
|
|
|
|
ExportedText = Archive;
|
|
}
|
|
|
|
// Imports a set of previously exported nodes into a graph
|
|
void FEdGraphUtilities::ImportNodesFromText(UEdGraph* DestinationGraph, const FString& TextToImport, /*out*/ TSet<UEdGraphNode*>& ImportedNodeSet)
|
|
{
|
|
// Turn the text buffer into objects
|
|
FGraphObjectTextFactory Factory(DestinationGraph);
|
|
Factory.ProcessBuffer(DestinationGraph, RF_Transactional, TextToImport);
|
|
|
|
// Fix up pin cross-links, etc...
|
|
FEdGraphUtilities::PostProcessPastedNodes(Factory.SpawnedNodes);
|
|
|
|
// If a pin link wasn't resolved by now it was connected to something outside the clipboard and should be cleared:
|
|
UEdGraphPin::ResolveAllPinReferences();
|
|
|
|
ImportedNodeSet.Append(Factory.SpawnedNodes);
|
|
}
|
|
|
|
bool FEdGraphUtilities::CanImportNodesFromText(const UEdGraph* DestinationGraph, const FString& TextToImport )
|
|
{
|
|
FGraphObjectTextFactory Factory(DestinationGraph);
|
|
return Factory.CanCreateObjectsFromText(TextToImport);
|
|
}
|
|
|
|
FIntRect FEdGraphUtilities::CalculateApproximateNodeBoundaries(const TArray<UEdGraphNode*>& Nodes)
|
|
{
|
|
int32 MinNodeX = +(int32)(1<<30);
|
|
int32 MinNodeY = +(int32)(1<<30);
|
|
int32 MaxNodeX = -(int32)(1<<30);
|
|
int32 MaxNodeY = -(int32)(1<<30);
|
|
|
|
for (auto NodeIt(Nodes.CreateConstIterator()); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* Node = *NodeIt;
|
|
if (Node)
|
|
{
|
|
// Update stats
|
|
MinNodeX = FMath::Min<int32>(MinNodeX, Node->NodePosX);
|
|
MinNodeY = FMath::Min<int32>(MinNodeY, Node->NodePosY);
|
|
MaxNodeX = FMath::Max<int32>(MaxNodeX, Node->NodePosX + Node->NodeWidth);
|
|
MaxNodeY = FMath::Max<int32>(MaxNodeY, Node->NodePosY + Node->NodeHeight);
|
|
}
|
|
}
|
|
|
|
const int32 AverageNodeWidth = 200;
|
|
const int32 AverageNodeHeight = 128;
|
|
|
|
return FIntRect(MinNodeX, MinNodeY, MaxNodeX + AverageNodeWidth, MaxNodeY + AverageNodeHeight);
|
|
}
|
|
|
|
void FEdGraphUtilities::CopyCommonState(UEdGraphNode* OldNode, UEdGraphNode* NewNode)
|
|
{
|
|
// Copy common inheritable state (comment, location, etc...)
|
|
NewNode->NodePosX = OldNode->NodePosX;
|
|
NewNode->NodePosY = OldNode->NodePosY;
|
|
NewNode->NodeWidth = OldNode->NodeWidth;
|
|
NewNode->NodeHeight = OldNode->NodeHeight;
|
|
NewNode->NodeComment = OldNode->NodeComment;
|
|
}
|
|
|
|
bool FEdGraphUtilities::IsSetParam(const UFunction* Function, const FName ParameterName)
|
|
{
|
|
if (Function == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FString& RawMetaData = Function->GetMetaData(FBlueprintMetadata::MD_SetParam);
|
|
if (RawMetaData.IsEmpty())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<FString> SetParamPinGroups;
|
|
{
|
|
RawMetaData.ParseIntoArray(SetParamPinGroups, TEXT(","), true);
|
|
}
|
|
|
|
if (SetParamPinGroups.Num() > 0)
|
|
{
|
|
TArray<FString> GroupEntries;
|
|
const FString ParameterNameStr = ParameterName.ToString();
|
|
for (const FString& Entry : SetParamPinGroups)
|
|
{
|
|
GroupEntries.Reset();
|
|
Entry.ParseIntoArray(GroupEntries, TEXT("|"), true);
|
|
if (GroupEntries.Contains(ParameterNameStr))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FEdGraphUtilities::IsMapParam(const UFunction* Function, const FName ParameterName)
|
|
{
|
|
if (Function == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FString& MapParamMetaData = Function->GetMetaData(FBlueprintMetadata::MD_MapParam);
|
|
const FString& MapValueParamMetaData = Function->GetMetaData(FBlueprintMetadata::MD_MapValueParam);
|
|
const FString& MapKeyParamMetaData = Function->GetMetaData(FBlueprintMetadata::MD_MapKeyParam);
|
|
if (MapParamMetaData.IsEmpty() && MapValueParamMetaData.IsEmpty() && MapKeyParamMetaData.IsEmpty() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<FString> GroupEntries;
|
|
const FString ParameterNameStr = ParameterName.ToString();
|
|
|
|
const auto PipeSeparatedStringContains = [&GroupEntries, &ParameterNameStr](const FString& List)
|
|
{
|
|
GroupEntries.Reset();
|
|
List.ParseIntoArray(GroupEntries, TEXT("|"), true);
|
|
if (GroupEntries.Contains(ParameterNameStr))
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
return PipeSeparatedStringContains(MapParamMetaData)
|
|
|| PipeSeparatedStringContains(MapValueParamMetaData)
|
|
|| PipeSeparatedStringContains(MapKeyParamMetaData);
|
|
}
|
|
|
|
bool FEdGraphUtilities::IsArrayDependentParam(const UFunction* Function, const FName ParameterName)
|
|
{
|
|
if (Function == nullptr)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
const FString& DependentPinMetaData = Function->GetMetaData(FBlueprintMetadata::MD_ArrayDependentParam);
|
|
if( DependentPinMetaData.IsEmpty() )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<FString> TypeDependentPinNames;
|
|
DependentPinMetaData.ParseIntoArray(TypeDependentPinNames, TEXT(","), true);
|
|
|
|
return TypeDependentPinNames.Contains(ParameterName.ToString());
|
|
}
|
|
|
|
bool FEdGraphUtilities::IsDynamicContainerParam(const UFunction* Function, const FName ParameterName)
|
|
{
|
|
return FEdGraphUtilities::IsArrayDependentParam(Function, ParameterName) || FEdGraphUtilities::IsMapParam(Function, ParameterName) || FEdGraphUtilities::IsSetParam(Function, ParameterName);
|
|
}
|
|
|
|
UEdGraphPin* FEdGraphUtilities::FindArrayParamPin(const UFunction* Function, const UEdGraphNode* Node)
|
|
{
|
|
return FEdGraphUtilities::FindPinFromMetaData(Function, Node, FBlueprintMetadata::MD_ArrayParam);
|
|
}
|
|
|
|
UEdGraphPin* FEdGraphUtilities::FindSetParamPin(const UFunction* Function, const UEdGraphNode* Node)
|
|
{
|
|
return FEdGraphUtilities::FindPinFromMetaData(Function, Node, FBlueprintMetadata::MD_SetParam);
|
|
}
|
|
|
|
UEdGraphPin* FEdGraphUtilities::FindMapParamPin(const UFunction* Function, const UEdGraphNode* Node)
|
|
{
|
|
return FEdGraphUtilities::FindPinFromMetaData(Function, Node, FBlueprintMetadata::MD_MapParam);
|
|
}
|
|
|
|
UEdGraphPin* FEdGraphUtilities::FindPinFromMetaData(const UFunction* Function, const UEdGraphNode* Node, FName MetaData )
|
|
{
|
|
if(!Function || !Node)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if(!Function->HasMetaData(MetaData))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
const FString& PinMetaData = Function->GetMetaData(MetaData);
|
|
TArray<FString> ParamPinGroups;
|
|
PinMetaData.ParseIntoArray(ParamPinGroups, TEXT(","), true);
|
|
|
|
for (const FString& Entry : ParamPinGroups)
|
|
{
|
|
// split the group:
|
|
TArray<FString> GroupEntries;
|
|
Entry.ParseIntoArray(GroupEntries, TEXT("|"), true);
|
|
// resolve pins
|
|
for(const FString& PinName : GroupEntries)
|
|
{
|
|
if(UEdGraphPin* Pin = Node->FindPin(PinName))
|
|
{
|
|
return Pin;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void FEdGraphUtilities::RegisterVisualNodeFactory(TSharedPtr<FGraphPanelNodeFactory> NewFactory)
|
|
{
|
|
VisualNodeFactories.Add(NewFactory);
|
|
}
|
|
|
|
void FEdGraphUtilities::UnregisterVisualNodeFactory(TSharedPtr<FGraphPanelNodeFactory> OldFactory)
|
|
{
|
|
VisualNodeFactories.Remove(OldFactory);
|
|
}
|
|
|
|
void FEdGraphUtilities::RegisterVisualPinFactory(TSharedPtr<FGraphPanelPinFactory> NewFactory)
|
|
{
|
|
VisualPinFactories.Add(NewFactory);
|
|
}
|
|
|
|
void FEdGraphUtilities::UnregisterVisualPinFactory(TSharedPtr<FGraphPanelPinFactory> OldFactory)
|
|
{
|
|
VisualPinFactories.Remove(OldFactory);
|
|
}
|
|
|
|
void FEdGraphUtilities::RegisterVisualPinConnectionFactory(TSharedPtr<FGraphPanelPinConnectionFactory> NewFactory)
|
|
{
|
|
VisualPinConnectionFactories.Add(NewFactory);
|
|
}
|
|
|
|
void FEdGraphUtilities::UnregisterVisualPinConnectionFactory(TSharedPtr<FGraphPanelPinConnectionFactory> OldFactory)
|
|
{
|
|
VisualPinConnectionFactories.Remove(OldFactory);
|
|
}
|
|
|
|
|
|
void FEdGraphUtilities::CopyPinDefaults(const UEdGraphNode* NodeFrom, UEdGraphNode* NodeTo)
|
|
{
|
|
check(NodeFrom && NodeTo);
|
|
|
|
UK2Node_EditablePinBase* WithUserDefinedPins = Cast<UK2Node_EditablePinBase>(NodeTo);
|
|
|
|
for (UEdGraphPin* ToPin : NodeTo->Pins)
|
|
{
|
|
if (UEdGraphPin * FromPin = NodeFrom->FindPin(ToPin->GetName()))
|
|
{
|
|
ToPin->DefaultValue = FromPin->DefaultValue;
|
|
ToPin->DefaultObject = FromPin->DefaultObject;
|
|
ToPin->DefaultTextValue = FromPin->DefaultTextValue;
|
|
|
|
if (WithUserDefinedPins)
|
|
{
|
|
// Copy defaults to user defined pins
|
|
WithUserDefinedPins->PinDefaultValueChanged(FromPin);
|
|
}
|
|
else
|
|
{
|
|
// No user defined pins implies we're copying to a function call node.
|
|
// Ensure that underlying changes to the entry node default are reflected to unmodified call nodes.
|
|
ToPin->AutogeneratedDefaultValue = FromPin->DefaultValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FEdGraphUtilities::GetPinConnectionMap(const UEdGraphNode* Node, TMap<FString, TSet<UEdGraphPin*>>& OutPinConnections)
|
|
{
|
|
check(Node);
|
|
|
|
for (UEdGraphPin* Pin : Node->Pins)
|
|
{
|
|
if (Pin != nullptr)
|
|
{
|
|
FString PinName = Pin->GetName();
|
|
|
|
// If this is the first time seeing this pin name, add a new set
|
|
if (!OutPinConnections.Contains(PinName))
|
|
{
|
|
OutPinConnections.Add(PinName, TSet<UEdGraphPin*>());
|
|
}
|
|
else
|
|
{
|
|
// There are no pins connected to this
|
|
continue;
|
|
}
|
|
|
|
for (UEdGraphPin* ConnectedPin : Pin->LinkedTo)
|
|
{
|
|
if (ConnectedPin != nullptr)
|
|
{
|
|
// Add to the array of nodes at this pin name
|
|
OutPinConnections[PinName].Add(ConnectedPin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FEdGraphUtilities::ReconnectPinMap(UEdGraphNode* Node, const TMap<FString, TSet<UEdGraphPin*>>& PinConnections)
|
|
{
|
|
check(Node);
|
|
|
|
for (UEdGraphPin* const NewPin : Node->Pins)
|
|
{
|
|
const FString& NewPinName = NewPin->GetName();
|
|
if (PinConnections.Contains(NewPinName))
|
|
{
|
|
// Connect the new pins here
|
|
for (UEdGraphPin* OldPin : PinConnections[NewPinName])
|
|
{
|
|
NewPin->MakeLinkTo(OldPin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FEdGraphUtilities::FNodeVisitor::TraverseNodes(UEdGraphNode* Node)
|
|
{
|
|
VisitedNodes.Add(Node);
|
|
TouchNode(Node);
|
|
|
|
// Follow every pin
|
|
for (int32 i = 0; i < Node->Pins.Num(); ++i)
|
|
{
|
|
UEdGraphPin* MyPin = Node->Pins[i];
|
|
|
|
// And every connection to the pin
|
|
for (int32 j = 0; j < MyPin->LinkedTo.Num(); ++j)
|
|
{
|
|
UEdGraphPin* OtherPin = MyPin->LinkedTo[j];
|
|
if (OtherPin)
|
|
{
|
|
UEdGraphNode* OtherNode = OtherPin->GetOwningNodeUnchecked();
|
|
if (OtherNode && !VisitedNodes.Contains(OtherNode))
|
|
{
|
|
TraverseNodes(OtherNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// FWeakGraphPinPtr
|
|
|
|
void FWeakGraphPinPtr::operator=(const class UEdGraphPin* Pin)
|
|
{
|
|
PinReference = Pin;
|
|
if(Pin && !Pin->IsPendingKill())
|
|
{
|
|
PinName = Pin->PinName;
|
|
NodeObjectPtr = Pin->GetOwningNode();
|
|
}
|
|
else
|
|
{
|
|
Reset();
|
|
}
|
|
}
|
|
|
|
UEdGraphPin* FWeakGraphPinPtr::Get()
|
|
{
|
|
if (UEdGraphNode* Node = NodeObjectPtr.Get())
|
|
{
|
|
// If pin is no longer valid or has a different owner, attempt to fix up the reference
|
|
UEdGraphPin* Pin = PinReference.Get();
|
|
if (Pin == nullptr || Pin->GetOuter() != Node)
|
|
{
|
|
for (UEdGraphPin* TestPin : Node->Pins)
|
|
{
|
|
if (TestPin->PinName == PinName)
|
|
{
|
|
Pin = TestPin;
|
|
PinReference = Pin;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Pin;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|