#if WITH_EDITOR #include "BlueprintExporter.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" FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph) : Graph(InGraph) { SortNodes(); AssignNodeNames(); EmitNodeList(); EmitGraph(); } void FlxBlueprintExporter::AssignNodeNames() { TMap NextIndex; for (UEdGraphNode* Node : SortedNodes) { FString Base = SanitizeName(Node->GetNodeTitle(ENodeTitleType::ListView).ToString()); 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::Traverse(UEdGraphNode* Node) { if (Visited.Contains(Node)) return; Visited.Add(Node); // First, traverse input nodes for (UEdGraphPin* Pin : Node->Pins) { if (Pin->Direction != EGPD_Input) continue; 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 : Node->Pins) { if (Pin->Direction != EGPD_Output) continue; if (Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) continue; for (UEdGraphPin* LinkedPin : Pin->LinkedTo) { Traverse(LinkedPin->GetOwningNode()); } } } bool FlxBlueprintExporter::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction) { for (UEdGraphPin* Pin : Node->Pins) { if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) { if (Pin->Direction == Direction) return true; } } return false; } 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::EmitNodeList() { Output.Appendf(TEXT("NodeList:\n")); for (UEdGraphNode* Node : SortedNodes) { if (Node->IsA()) continue; if (Node->IsA()) continue; if (Node->IsA()) continue; Output.Appendf(TEXT(" %s = %s\n"), *NodeNames[Node], *Node->NodeGuid.ToString()); } } FString FlxBlueprintExporter::GetPinDisplayName(UEdGraphPin *Pin) { FString Result = Pin->GetOwningNode()->GetPinDisplayName(Pin).ToString(); if (!Result.IsEmpty()) return Result; return Pin->PinName.ToString(); } UEdGraphPin* FlxBlueprintExporter::FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction) { for (UEdGraphPin* Pin : Node->Pins) { if (Pin->Direction == Direction) return Pin; } return nullptr; } 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); } } FString FlxBlueprintExporter::FormatPinType(UEdGraphPin* Pin) { if (UObject* SubObj = Pin->PinType.PinSubCategoryObject.Get()) { return SubObj->GetName(); } FString Type = Pin->PinType.PinCategory.ToString(); Type[0] = FChar::ToUpper(Type[0]); return Type; } 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 = SanitizeName(GetPinDisplayName(LinkedTo)); FString* Name = NodeNames.Find(LinkedToNode); return FString::Printf(TEXT("%s.%s"), **Name, *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::EmitNode(UEdGraphNode* Node) { if (Node->IsA()) { Output.Appendf(TEXT("\n // %s\n"), *Node->NodeComment); return; } if (Node->IsA()) return; Output.Appendf(TEXT("\n %s\n"), *NodeNames[Node]); // Emit input data pins. for (UEdGraphPin* Pin : Node->Pins) { if (Pin->Direction != EGPD_Input) continue; if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue; if (Pin->bHidden) continue; Output.Appendf(TEXT(" %s %s = %s\n"), *FormatPinType(Pin), *SanitizeName(GetPinDisplayName(Pin)), *FormatPinSource(Pin)); } // Emit output data pins as a return line. FString ReturnPins; for (UEdGraphPin* Pin : Node->Pins) { if (Pin->Direction != EGPD_Output) continue; if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue; if (Pin->bHidden) continue; if (!ReturnPins.IsEmpty()) ReturnPins += TEXT(","); ReturnPins += SanitizeName(GetPinDisplayName(Pin)); } if (!ReturnPins.IsEmpty()) Output.Appendf(TEXT(" return %s\n"), *ReturnPins); // Detect output exec pin patterns. int32 ExecOutCount = 0; bool HasElsePin = false; for (UEdGraphPin* Pin : Node->Pins) { if (Pin->Direction != EGPD_Output) continue; if (Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) continue; ExecOutCount++; if (Pin->PinName == TEXT("Else")) HasElsePin = true; } // Emit output exec pins as goto statements. for (UEdGraphPin* Pin : Node->Pins) { if (Pin->Direction != EGPD_Output) continue; if (Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) continue; UEdGraphPin* LinkedTo = GetLinkedTo(Pin); if (!LinkedTo) continue; FString* TargetName = NodeNames.Find(LinkedTo->GetOwningNode()); FString Target = TargetName ? *TargetName : TEXT("?"); if (ExecOutCount == 1) Output.Appendf(TEXT(" goto %s\n"), *Target); else if (HasElsePin) Output.Appendf(TEXT(" %s goto %s\n"), *Pin->PinName.ToString().ToLower(), *Target); else Output.Appendf(TEXT(" goto %s if %s\n"), *Target, *Pin->PinName.ToString()); } } void FlxBlueprintExporter::EmitGraph() { Output.Appendf(TEXT("\nGraph:\n")); for (UEdGraphNode* Node : SortedNodes) { if (Node->IsA()) continue; EmitNode(Node); } } bool FlxBlueprintExporter::IsDefaultToSelf(UEdGraphPin* Pin) { // The engine's own IsSelfPin check. if (Pin->PinName == UEdGraphSchema_K2::PN_Self) return true; // Check if the owning node is a function call with DefaultToSelf metadata naming this pin. UK2Node_CallFunction* CallNode = Cast(Pin->GetOwningNode()); if (!CallNode) return false; UFunction* Function = CallNode->GetTargetFunction(); if (!Function) return false; const FString& DefaultToSelfPinName = Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf); return Pin->PinName.ToString() == DefaultToSelfPinName; } FString FlxBlueprintExporter::SanitizeName(const FString& Title) { FString Result; bool PrevWasUnderscore = true; // suppress leading underscore for (TCHAR Ch : Title) { if (FChar::IsAlnum(Ch)) { Result += Ch; PrevWasUnderscore = false; } else if (!PrevWasUnderscore) { Result += TEXT('_'); PrevWasUnderscore = true; } } // Trim trailing underscore if (Result.Len() > 0 && Result[Result.Len() - 1] == TEXT('_')) { Result.LeftChopInline(1); } return Result.IsEmpty() ? TEXT("_") : Result; } #endif