398 lines
11 KiB
C++
398 lines
11 KiB
C++
#if WITH_EDITOR
|
|
|
|
#include "BlueprintExporter.h"
|
|
#include "Common.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraph/EdGraphNode.h"
|
|
#include "EdGraph/EdGraphPin.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "K2Node_Knot.h"
|
|
#include "EdGraphNode_Comment.h"
|
|
#include "K2Node_VariableGet.h"
|
|
#include "K2Node_CallFunction.h"
|
|
#include "K2Node_FunctionEntry.h"
|
|
|
|
FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph)
|
|
: Graph(InGraph)
|
|
{
|
|
SortNodes();
|
|
AssignNodeNames();
|
|
EmitLocalVariables();
|
|
EmitNodeList();
|
|
EmitGraph();
|
|
}
|
|
|
|
////////////////////////////////////////////////////////
|
|
//
|
|
// General utilities for manipulating UEdGraph nodes.
|
|
//
|
|
////////////////////////////////////////////////////////
|
|
|
|
FString FlxBlueprintExporter::SanitizeName(const FString& Title)
|
|
{
|
|
FString Result = Title.TrimStartAndEnd().Replace(TEXT(" "), TEXT("_"));
|
|
return Result.IsEmpty() ? TEXT("_") : Result;
|
|
}
|
|
|
|
FString FlxBlueprintExporter::FormatPinType(const FEdGraphPinType& PinType)
|
|
{
|
|
if (UObject* SubObj = PinType.PinSubCategoryObject.Get())
|
|
{
|
|
return SubObj->GetName();
|
|
}
|
|
FString Type = PinType.PinCategory.ToString();
|
|
Type[0] = FChar::ToUpper(Type[0]);
|
|
return Type;
|
|
}
|
|
|
|
FString FlxBlueprintExporter::FormatPinType(UEdGraphPin* Pin)
|
|
{
|
|
return FormatPinType(Pin->PinType);
|
|
}
|
|
|
|
FString FlxBlueprintExporter::FormatNodeBaseName(UEdGraphNode* Node)
|
|
{
|
|
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
|
int32 NewlineIdx;
|
|
if (Title.FindChar(TEXT('\n'), NewlineIdx))
|
|
Title.LeftInline(NewlineIdx);
|
|
return SanitizeName(Title);
|
|
}
|
|
|
|
UEdGraphPin* FlxBlueprintExporter::GetLinkedTo(UEdGraphPin* Pin)
|
|
{
|
|
while (true)
|
|
{
|
|
if (Pin == nullptr) return nullptr;
|
|
if (Pin->LinkedTo.IsEmpty()) return nullptr;
|
|
UEdGraphPin *LinkedTo = Pin->LinkedTo[0];
|
|
if (!LinkedTo->GetOwningNode()->IsA<UK2Node_Knot>()) return LinkedTo;
|
|
Pin = FindFirstPin(LinkedTo->GetOwningNode(), Pin->Direction);
|
|
}
|
|
}
|
|
|
|
bool FlxBlueprintExporter::IsDefaultToSelf(UEdGraphPin* Pin)
|
|
{
|
|
// Only valid call-function nodes can have default-to-self.
|
|
UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Pin->GetOwningNode());
|
|
if (!CallNode) return false;
|
|
UFunction* Function = CallNode->GetTargetFunction();
|
|
if (!Function) return false;
|
|
|
|
// In a call function node, the pin name 'self' is reserved
|
|
// for the C++ 'this' pointer. This always has 'DefaultToSelf'
|
|
// behavior. Note that 'self' in other nodes is not special.
|
|
if (Pin->PinName == UEdGraphSchema_K2::PN_Self) return true;
|
|
|
|
// For any other pin name, we have to check for
|
|
// the presence of meta = (DefaultToSelf = "Pin")
|
|
const FString& DefaultToSelfPinName = Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf);
|
|
return Pin->PinName.ToString() == DefaultToSelfPinName;
|
|
}
|
|
|
|
TArray<UEdGraphPin*> FlxBlueprintExporter::FilterPins(UEdGraphNode* Node, EEdGraphPinDirection Direction, FName Category)
|
|
{
|
|
TArray<UEdGraphPin*> Result;
|
|
for (UEdGraphPin* Pin : Node->Pins)
|
|
{
|
|
if (Direction != EGPD_MAX && Pin->Direction != Direction) continue;
|
|
if (!Category.IsNone() && Pin->PinType.PinCategory != Category) continue;
|
|
Result.Add(Pin);
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
bool FlxBlueprintExporter::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
|
{
|
|
return FilterPins(Node, Direction, UEdGraphSchema_K2::PC_Exec).Num() > 0;
|
|
}
|
|
|
|
UEdGraphPin* FlxBlueprintExporter::FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
|
{
|
|
for (UEdGraphPin* Pin : Node->Pins)
|
|
{
|
|
if (Pin->Direction == Direction) return Pin;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UEdGraphPin* FlxBlueprintExporter::BestMatchPin(UEdGraphNode* Node, EEdGraphPinDirection Direction, bool Exec, const FString& Name)
|
|
{
|
|
if (Name == TEXT("_")) return nullptr;
|
|
|
|
UEdGraphPin* DisplayMatch = nullptr;
|
|
int32 DisplayCount = 0;
|
|
UEdGraphPin* RawMatch = nullptr;
|
|
int32 RawCount = 0;
|
|
|
|
for (UEdGraphPin* Pin : Node->Pins)
|
|
{
|
|
if (Pin->Direction != Direction) continue;
|
|
bool PinIsExec = (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec);
|
|
if (PinIsExec != Exec) continue;
|
|
|
|
if (Name == SanitizeName(Node->GetPinDisplayName(Pin).ToString()))
|
|
{
|
|
DisplayMatch = Pin;
|
|
DisplayCount++;
|
|
}
|
|
|
|
if (Name == SanitizeName(Pin->PinName.ToString()))
|
|
{
|
|
RawMatch = Pin;
|
|
RawCount++;
|
|
}
|
|
}
|
|
|
|
if (DisplayCount == 1) return DisplayMatch;
|
|
if (RawCount == 1) return RawMatch;
|
|
return nullptr;
|
|
}
|
|
|
|
FString FlxBlueprintExporter::FormatPinName(UEdGraphPin *Pin)
|
|
{
|
|
UEdGraphNode* Node = Pin->GetOwningNode();
|
|
bool Exec = (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec);
|
|
|
|
// Try sanitized display name first.
|
|
FString SanitizedDisplay = SanitizeName(Node->GetPinDisplayName(Pin).ToString());
|
|
if (BestMatchPin(Node, Pin->Direction, Exec, SanitizedDisplay) == Pin)
|
|
return SanitizedDisplay;
|
|
|
|
// Try sanitized raw name.
|
|
FString SanitizedRaw = SanitizeName(Pin->PinName.ToString());
|
|
if (BestMatchPin(Node, Pin->Direction, Exec, SanitizedRaw) == Pin)
|
|
return SanitizedRaw;
|
|
|
|
// No unambiguous name found.
|
|
UE_LOG(LogLuprexIntegration, Warning, TEXT("Blueprint export: ambiguous pin name '%s' on node '%s'"),
|
|
*Pin->PinName.ToString(), *Node->GetNodeTitle(ENodeTitleType::ListView).ToString());
|
|
return FString::Printf(TEXT("?%s"), *SanitizedRaw);
|
|
}
|
|
|
|
|
|
////////////////////////////////////////////////////////
|
|
//
|
|
// Traverse and Emit the Nodes.
|
|
//
|
|
////////////////////////////////////////////////////////
|
|
|
|
FString FlxBlueprintExporter::FormatNodeName(UEdGraphNode* Node)
|
|
{
|
|
FString* Name = NodeNames.Find(Node);
|
|
return Name ? *Name : FormatNodeBaseName(Node);
|
|
}
|
|
|
|
FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
|
{
|
|
// If connected, show source node.pin
|
|
UEdGraphPin* LinkedTo = GetLinkedTo(Pin);
|
|
if (LinkedTo != nullptr)
|
|
{
|
|
UEdGraphNode* LinkedToNode = LinkedTo->GetOwningNode();
|
|
|
|
// For variable get nodes, just show the variable name.
|
|
if (UK2Node_VariableGet* VarGet = Cast<UK2Node_VariableGet>(LinkedToNode))
|
|
return SanitizeName(VarGet->GetVarNameString());
|
|
|
|
FString PinLabel = FormatPinName(LinkedTo);
|
|
return FString::Printf(TEXT("%s.%s"), *FormatNodeName(LinkedToNode), *PinLabel);
|
|
}
|
|
|
|
// String pins: always show in quotes (even if empty).
|
|
FName Category = Pin->PinType.PinCategory;
|
|
if (Category == UEdGraphSchema_K2::PC_String ||
|
|
Category == UEdGraphSchema_K2::PC_Name ||
|
|
Category == UEdGraphSchema_K2::PC_Text)
|
|
{
|
|
return FString::Printf(TEXT("\"%s\""), *Pin->DefaultValue);
|
|
}
|
|
|
|
// If has a non-empty default, show it.
|
|
if (!Pin->DefaultValue.IsEmpty())
|
|
{
|
|
return Pin->DefaultValue;
|
|
}
|
|
|
|
if (Pin->DefaultObject)
|
|
{
|
|
return Pin->DefaultObject->GetName();
|
|
}
|
|
|
|
if (!Pin->DefaultTextValue.IsEmpty())
|
|
{
|
|
return FString::Printf(TEXT("\"%s\""), *Pin->DefaultTextValue.ToString());
|
|
}
|
|
|
|
if (IsDefaultToSelf(Pin))
|
|
{
|
|
return TEXT("<self>");
|
|
}
|
|
else
|
|
{
|
|
return TEXT("<default>");
|
|
}
|
|
}
|
|
|
|
void FlxBlueprintExporter::Traverse(UEdGraphNode* Node)
|
|
{
|
|
if (Visited.Contains(Node)) return;
|
|
Visited.Add(Node);
|
|
|
|
// First, traverse input nodes
|
|
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
|
|
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
|
Traverse(LinkedPin->GetOwningNode());
|
|
|
|
// Add this node to the sorted list.
|
|
SortedNodes.Add(Node);
|
|
|
|
// Then, traverse exec output nodes only.
|
|
// Data outputs are not followed — data nodes get pulled in
|
|
// through their consumers' input traversal.
|
|
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Output, UEdGraphSchema_K2::PC_Exec))
|
|
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
|
Traverse(LinkedPin->GetOwningNode());
|
|
}
|
|
|
|
void FlxBlueprintExporter::SortNodes()
|
|
{
|
|
// Find starter nodes: have exec output but no exec input.
|
|
TArray<UEdGraphNode*> Starters;
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
{
|
|
if (HasExecPin(Node, EGPD_Output) && !HasExecPin(Node, EGPD_Input))
|
|
{
|
|
Starters.Add(Node);
|
|
}
|
|
}
|
|
|
|
// Sort starters by Y position.
|
|
Starters.Sort([](const UEdGraphNode& A, const UEdGraphNode& B)
|
|
{
|
|
return A.NodePosY < B.NodePosY;
|
|
});
|
|
|
|
// Traverse from each starter.
|
|
for (UEdGraphNode* Starter : Starters)
|
|
{
|
|
Traverse(Starter);
|
|
}
|
|
|
|
// Traverse all nodes.
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
{
|
|
Traverse(Node);
|
|
}
|
|
}
|
|
|
|
void FlxBlueprintExporter::AssignNodeNames()
|
|
{
|
|
TMap<FString, int32> NextIndex;
|
|
|
|
for (UEdGraphNode* Node : SortedNodes)
|
|
{
|
|
FString Base = FormatNodeBaseName(Node);
|
|
int32& Idx = NextIndex.FindOrAdd(Base, 0);
|
|
FString Name = (Idx == 0) ? Base : FString::Printf(TEXT("%s_%d"), *Base, Idx + 1);
|
|
NodeNames.Add(Node, Name);
|
|
Idx++;
|
|
}
|
|
}
|
|
|
|
void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
|
{
|
|
if (Node->IsA<UEdGraphNode_Comment>())
|
|
{
|
|
Output.Appendf(TEXT("\n// %s\n"), *Node->NodeComment);
|
|
return;
|
|
}
|
|
|
|
Output.Appendf(TEXT("\nnode %s\n"), *FormatNodeName(Node));
|
|
|
|
// Emit input data pins.
|
|
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
|
|
{
|
|
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;
|
|
if (Pin->bHidden) continue;
|
|
|
|
Output.Appendf(TEXT(" input %s %s = %s\n"),
|
|
*FormatPinType(Pin),
|
|
*FormatPinName(Pin),
|
|
*FormatPinSource(Pin));
|
|
}
|
|
|
|
// Emit output data pins as a return line.
|
|
FString ReturnPins;
|
|
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Output))
|
|
{
|
|
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;
|
|
if (Pin->bHidden) continue;
|
|
if (!ReturnPins.IsEmpty()) ReturnPins += TEXT(", ");
|
|
ReturnPins += FormatPinName(Pin);
|
|
}
|
|
if (!ReturnPins.IsEmpty())
|
|
{
|
|
Output.Appendf(TEXT(" return %s\n"), *ReturnPins);
|
|
}
|
|
|
|
// Emit output exec pins as goto statements.
|
|
TArray<UEdGraphPin*> ExecOuts = FilterPins(Node, EGPD_Output, UEdGraphSchema_K2::PC_Exec);
|
|
for (UEdGraphPin* Pin : ExecOuts)
|
|
{
|
|
UEdGraphPin* LinkedTo = GetLinkedTo(Pin);
|
|
if (!LinkedTo) continue;
|
|
|
|
FString Target = FormatNodeName(LinkedTo->GetOwningNode());
|
|
|
|
if (ExecOuts.Num() == 1)
|
|
Output.Appendf(TEXT(" goto %s\n"), *Target);
|
|
else
|
|
Output.Appendf(TEXT(" goto-if %s %s\n"), *FormatPinName(Pin), *Target);
|
|
}
|
|
}
|
|
|
|
void FlxBlueprintExporter::EmitLocalVariables()
|
|
{
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
{
|
|
UK2Node_FunctionEntry* EntryNode = Cast<UK2Node_FunctionEntry>(Node);
|
|
if (!EntryNode) continue;
|
|
|
|
for (const FBPVariableDescription& Var : EntryNode->LocalVariables)
|
|
{
|
|
FString Default = Var.DefaultValue.IsEmpty() ? TEXT("<default>") : Var.DefaultValue;
|
|
Output.Appendf(TEXT("local %s %s = %s\n"),
|
|
*FormatPinType(Var.VarType),
|
|
*SanitizeName(Var.VarName.ToString()),
|
|
*Default);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void FlxBlueprintExporter::EmitGraph()
|
|
{
|
|
for (UEdGraphNode* Node : SortedNodes)
|
|
{
|
|
if (Node->IsA<UK2Node_Knot>()) continue;
|
|
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
|
EmitNode(Node);
|
|
}
|
|
}
|
|
|
|
void FlxBlueprintExporter::EmitNodeList()
|
|
{
|
|
for (UEdGraphNode* Node : SortedNodes)
|
|
{
|
|
if (Node->IsA<UK2Node_Knot>()) continue;
|
|
if (Node->IsA<UEdGraphNode_Comment>()) continue;
|
|
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
|
Details.Appendf(TEXT("%s = %s\n"),
|
|
*FormatNodeName(Node), *Node->NodeGuid.ToString());
|
|
}
|
|
}
|
|
|
|
#endif
|