GraphNode_Dump, GraphNode_ShowMenu, GraphNode_ChooseMenu now all functional.
This commit is contained in:
372
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/GraphExport.cpp
Normal file
372
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/GraphExport.cpp
Normal file
@@ -0,0 +1,372 @@
|
||||
#include "GraphExport.h"
|
||||
#include "MCPTypes.h"
|
||||
#include "MCPUtils.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"
|
||||
#include "MaterialGraph/MaterialGraphNode.h"
|
||||
|
||||
MCPGraphExport::MCPGraphExport(UEdGraph* InGraph)
|
||||
: Graph(InGraph)
|
||||
{
|
||||
SortNodes();
|
||||
EmitLocalVariables();
|
||||
EmitDetails();
|
||||
EmitGraph();
|
||||
EmitComments();
|
||||
}
|
||||
|
||||
MCPGraphExport::MCPGraphExport(UEdGraphNode* InNode)
|
||||
: Graph(InNode->GetGraph())
|
||||
{
|
||||
SortedNodes.Add(InNode);
|
||||
Visited.Add(InNode);
|
||||
EmitLocalVariables();
|
||||
EmitDetails();
|
||||
EmitGraph();
|
||||
EmitComments();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// General utilities for manipulating UEdGraph nodes.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
UEdGraphPin* MCPGraphExport::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 MCPGraphExport::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*> MCPGraphExport::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 MCPGraphExport::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
||||
{
|
||||
return FilterPins(Node, Direction, UEdGraphSchema_K2::PC_Exec).Num() > 0;
|
||||
}
|
||||
|
||||
UEdGraphPin* MCPGraphExport::FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
||||
{
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin->Direction == Direction) return Pin;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Traverse and Emit the Nodes.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
FString MCPGraphExport::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 MCPUtils::FormatName(VarGet->VariableReference);
|
||||
|
||||
FString PinLabel = MCPUtils::FormatName(LinkedTo);
|
||||
return FString::Printf(TEXT("%s.%s"), *MCPUtils::FormatName(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 MCPGraphExport::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 MCPGraphExport::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 MCPGraphExport::EmitNode(UEdGraphNode* Node)
|
||||
{
|
||||
if (Node->IsA<UEdGraphNode_Comment>()) return;
|
||||
|
||||
Output.Appendf(TEXT("\nnode %s: %s\n"), *MCPUtils::FormatName(Node), *MCPUtils::FormatNodeTitle(Node));
|
||||
|
||||
// Emit material expression properties (if applicable).
|
||||
EmitMaterialProperties(Node, Output, true);
|
||||
|
||||
// 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"),
|
||||
*UMCPTypes::TypeToText(Pin->PinType),
|
||||
*MCPUtils::FormatName(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 += MCPUtils::FormatName(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 = MCPUtils::FormatName(LinkedTo->GetOwningNode());
|
||||
|
||||
if (ExecOuts.Num() == 1)
|
||||
Output.Appendf(TEXT(" goto %s\n"), *Target);
|
||||
else
|
||||
Output.Appendf(TEXT(" goto-if %s %s\n"), *MCPUtils::FormatName(Pin), *Target);
|
||||
}
|
||||
}
|
||||
|
||||
void MCPGraphExport::EmitMaterialProperty(UMaterialExpression* Expression, FProperty* Prop, FStringBuilderBase& Out)
|
||||
{
|
||||
FString ValueStr = MCPUtils::GetPropertyValueText(Expression, Prop);
|
||||
ValueStr.ReplaceInline(TEXT("\r\n"), TEXT(" "));
|
||||
ValueStr.ReplaceInline(TEXT("\n"), TEXT(" "));
|
||||
if (ValueStr.Len() > 80)
|
||||
ValueStr = ValueStr.Left(80) + TEXT("...");
|
||||
|
||||
bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst);
|
||||
Out.Appendf(TEXT(" %s %s %s = %s\n"),
|
||||
bEditable ? TEXT("mxeditable") : TEXT("mxreadonly"),
|
||||
*UMCPTypes::TypeToText(Prop),
|
||||
*MCPUtils::FormatName(Prop),
|
||||
*ValueStr);
|
||||
}
|
||||
|
||||
void MCPGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary)
|
||||
{
|
||||
UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node);
|
||||
if (!MatNode || !MatNode->MaterialExpression) return;
|
||||
|
||||
UMaterialExpression* Expression = MatNode->MaterialExpression;
|
||||
FString PrimaryCategory = Expression->GetClass()->GetName();
|
||||
TArray<FProperty*> Props = MCPUtils::SearchProperties(Expression, FString(), CPF_Edit, false);
|
||||
|
||||
for (FProperty* Prop : Props)
|
||||
{
|
||||
FString Category = Prop->HasMetaData(TEXT("Category")) ? Prop->GetMetaData(TEXT("Category")) : FString();
|
||||
if ((Category == PrimaryCategory) == bPrimary)
|
||||
EmitMaterialProperty(Expression, Prop, Out);
|
||||
}
|
||||
}
|
||||
|
||||
void MCPGraphExport::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"),
|
||||
*UMCPTypes::TypeToText(Var.VarType),
|
||||
*MCPUtils::FormatName(Var),
|
||||
*Default);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MCPGraphExport::EmitGraph()
|
||||
{
|
||||
for (UEdGraphNode* Node : SortedNodes)
|
||||
{
|
||||
if (Node->IsA<UK2Node_Knot>()) continue;
|
||||
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
||||
EmitNode(Node);
|
||||
}
|
||||
Output.Append(TEXT("\n"));
|
||||
}
|
||||
|
||||
void MCPGraphExport::EmitDetails()
|
||||
{
|
||||
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("\ndetails %s\n"), *MCPUtils::FormatName(Node));
|
||||
Details.Appendf(TEXT(" pos %d, %d\n"), Node->NodePosX, Node->NodePosY);
|
||||
|
||||
EmitMaterialProperties(Node, Details, false);
|
||||
}
|
||||
}
|
||||
|
||||
void MCPGraphExport::EmitComments()
|
||||
{
|
||||
for (UEdGraphNode* CommentNode : SortedNodes)
|
||||
{
|
||||
if (!CommentNode->IsA<UEdGraphNode_Comment>()) continue;
|
||||
|
||||
int32 CX = CommentNode->NodePosX;
|
||||
int32 CY = CommentNode->NodePosY;
|
||||
int32 CW = CommentNode->NodeWidth;
|
||||
int32 CH = CommentNode->NodeHeight;
|
||||
|
||||
// Emit header.
|
||||
Output.Appendf(TEXT("\ncomment %s:\n"), *MCPUtils::FormatName(CommentNode));
|
||||
|
||||
// Emit wrapped, indented body.
|
||||
Output.Append(MCPUtils::WrapText(CommentNode->NodeComment, 70, TEXT(" - ")));
|
||||
Output.Append(TEXT("\n"));
|
||||
|
||||
// Find contained nodes.
|
||||
TArray<FString> ContainedNames;
|
||||
for (UEdGraphNode* Node : SortedNodes)
|
||||
{
|
||||
if (Node->IsA<UEdGraphNode_Comment>()) continue;
|
||||
int32 NX = Node->NodePosX;
|
||||
int32 NY = Node->NodePosY;
|
||||
if (NX >= CX && NY >= CY && NX <= CX + CW && NY <= CY + CH)
|
||||
ContainedNames.Add(MCPUtils::FormatName(Node));
|
||||
}
|
||||
|
||||
if (ContainedNames.Num() > 0)
|
||||
{
|
||||
Output.Appendf(TEXT(" applies to: %s\n"), *FString::Join(ContainedNames, TEXT(", ")));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user