#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()) 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(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 FlxBlueprintExporter::FilterPins(UEdGraphNode* Node, EEdGraphPinDirection Direction, FName Category) { TArray 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(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(""); } else { return TEXT(""); } } 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 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 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()) { 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 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(Node); if (!EntryNode) continue; for (const FBPVariableDescription& Var : EntryNode->LocalVariables) { FString Default = Var.DefaultValue.IsEmpty() ? TEXT("") : 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()) continue; if (Node->IsA()) continue; EmitNode(Node); } } void FlxBlueprintExporter::EmitNodeList() { for (UEdGraphNode* Node : SortedNodes) { if (Node->IsA()) continue; if (Node->IsA()) continue; if (Node->IsA()) continue; Details.Appendf(TEXT("%s = %s\n"), *FormatNodeName(Node), *Node->NodeGuid.ToString()); } } #endif