GraphNode_Dump, GraphNode_ShowMenu, GraphNode_ChooseMenu now all functional.
This commit is contained in:
Binary file not shown.
@@ -54,7 +54,7 @@ void UBlueprintExportSubsystem::OnAssetSaved(const FString& PackageFilename, UPa
|
|||||||
BP->GetAllGraphs(AllGraphs);
|
BP->GetAllGraphs(AllGraphs);
|
||||||
for (UEdGraph* Graph : AllGraphs)
|
for (UEdGraph* Graph : AllGraphs)
|
||||||
{
|
{
|
||||||
FlxBlueprintExporter Exporter(Graph);
|
MCPGraphExport Exporter(Graph);
|
||||||
|
|
||||||
FString FilePath = BPDir / Graph->GetName() + TEXT(".txt");
|
FString FilePath = BPDir / Graph->GetName() + TEXT(".txt");
|
||||||
FString DetailsPath = BPDir / TEXT("DETAILS") / Graph->GetName() + TEXT(".txt");
|
FString DetailsPath = BPDir / TEXT("DETAILS") / Graph->GetName() + TEXT(".txt");
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPToolMenu.h"
|
||||||
|
#include "MCPServer.h"
|
||||||
|
#include "ToolMenus.h"
|
||||||
|
#include "GraphNode_ChooseMenu.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCP_GraphNode_ChooseMenu : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Target node"))
|
||||||
|
FString Node;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Menu item as shown by GraphNode_ShowMenu"))
|
||||||
|
FString Item;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Execute a context menu action on a node or pin. "
|
||||||
|
"Supports SplitStructPin, AddPin, AddArrayElementPin, etc. "
|
||||||
|
"Use GraphNode_ShowMenu to see available actions. ");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void Handle() override
|
||||||
|
{
|
||||||
|
MCPFetcher F;
|
||||||
|
UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>();
|
||||||
|
if (!NodeObj) return;
|
||||||
|
|
||||||
|
FToolMenuContext Context;
|
||||||
|
TArray<FToolMenuEntry> Entries = MCPToolMenu::GetMenuItems(NodeObj, Context);
|
||||||
|
for (FToolMenuEntry &Entry : Entries)
|
||||||
|
{
|
||||||
|
FString LabelText = Entry.Label.Get().ToString();
|
||||||
|
if (!LabelText.Equals(Item, ESearchCase::IgnoreCase))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (MCPToolMenu::Execute(Entry, Context))
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("Executed: %s\n"), *LabelText);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: Action '%s' cannot execute (greyed out)\n"), *LabelText);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: Menu item '%s' not found. Use GraphNode_ShowMenu to see available items.\n"), *Item);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPServer.h"
|
||||||
|
#include "MCPFetcher.h"
|
||||||
|
#include "GraphExport.h"
|
||||||
|
#include "GraphNode_Dump.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCP_GraphNode_Dump : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Target node"))
|
||||||
|
FString Node;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Dump a single node as readable text, including all pins and connections.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void Handle() override
|
||||||
|
{
|
||||||
|
MCPFetcher F;
|
||||||
|
UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>();
|
||||||
|
if (!NodeObj) return;
|
||||||
|
|
||||||
|
MCPGraphExport Exporter(NodeObj);
|
||||||
|
UMCPServer::Print(*Exporter.GetOutput());
|
||||||
|
UMCPServer::Print(Exporter.GetDetails());
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -4,11 +4,7 @@
|
|||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
#include "MCPToolMenu.h"
|
#include "MCPToolMenu.h"
|
||||||
#include "MCPUtils.h"
|
|
||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "BlueprintEditor.h"
|
|
||||||
#include "EdGraph/EdGraphNode.h"
|
|
||||||
#include "EdGraph/EdGraphSchema.h"
|
|
||||||
#include "ToolMenus.h"
|
#include "ToolMenus.h"
|
||||||
#include "GraphNode_ShowMenu.generated.h"
|
#include "GraphNode_ShowMenu.generated.h"
|
||||||
|
|
||||||
@@ -31,82 +27,19 @@ public:
|
|||||||
return TEXT("Show context menu actions available for a node and its pins.");
|
return TEXT("Show context menu actions available for a node and its pins.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
virtual void Handle() override
|
virtual void Handle() override
|
||||||
{
|
{
|
||||||
MCPFetcher F;
|
MCPFetcher F;
|
||||||
UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
|
UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>();
|
||||||
if (!FoundNode) return;
|
if (!NodeObj) return;
|
||||||
|
|
||||||
// Get the blueprint editor's command list to build a proper context.
|
FToolMenuContext Context;
|
||||||
FBlueprintEditor* Editor = F.CastEditor<UBlueprint, FBlueprintEditor>();
|
TArray<FToolMenuEntry> Entries = MCPToolMenu::GetMenuItems(NodeObj, Context);
|
||||||
const TSharedPtr<FUICommandList>& CommandList = Editor
|
for (FToolMenuEntry &Entry : Entries)
|
||||||
? MCPToolMenu::GetGraphEditorCommands(*Editor) : EmptyCommandList;
|
|
||||||
FToolMenuContext MenuContext(CommandList);
|
|
||||||
|
|
||||||
UEdGraph* Graph = FoundNode->GetGraph();
|
|
||||||
if (!Graph) return;
|
|
||||||
|
|
||||||
const UEdGraphSchema* Schema = Graph->GetSchema();
|
|
||||||
if (!Schema) return;
|
|
||||||
|
|
||||||
// Print actions for the node itself (no pin).
|
|
||||||
PrintActions(FoundNode, Graph, Schema, nullptr, MenuContext);
|
|
||||||
|
|
||||||
// Print actions for each pin.
|
|
||||||
for (UEdGraphPin* Pin : FoundNode->Pins)
|
|
||||||
{
|
{
|
||||||
PrintActions(FoundNode, Graph, Schema, Pin, MenuContext);
|
FString LabelText = Entry.Label.Get().ToString();
|
||||||
|
UMCPServer::Printf(TEXT("%s\n"), *LabelText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
|
||||||
|
|
||||||
TSharedPtr<FUICommandList> EmptyCommandList;
|
|
||||||
|
|
||||||
void PrintMenu(UToolMenu* Menu, const TCHAR* Header, const FToolMenuContext& MenuContext)
|
|
||||||
{
|
|
||||||
bool bAnyEntries = false;
|
|
||||||
for (const FToolMenuSection& Section : Menu->Sections)
|
|
||||||
{
|
|
||||||
for (const FToolMenuEntry& Entry : Section.Blocks)
|
|
||||||
{
|
|
||||||
FText Label = Entry.Label.Get();
|
|
||||||
if (Label.IsEmpty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
bool bCanExecute = MCPToolMenu::CanExecute(Entry, MenuContext);
|
|
||||||
|
|
||||||
if (!bAnyEntries)
|
|
||||||
UMCPServer::Printf(TEXT(" %s:\n"), Header);
|
|
||||||
if (bCanExecute)
|
|
||||||
UMCPServer::Printf(TEXT(" %s (can execute)\n"), *Label.ToString());
|
|
||||||
else
|
|
||||||
UMCPServer::Printf(TEXT(" %s\n"), *Label.ToString());
|
|
||||||
bAnyEntries = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PrintActions(UEdGraphNode* FoundNode, UEdGraph* Graph, const UEdGraphSchema* Schema,
|
|
||||||
const UEdGraphPin* Pin, const FToolMenuContext& MenuContext)
|
|
||||||
{
|
|
||||||
UGraphNodeContextMenuContext* Context = NewObject<UGraphNodeContextMenuContext>();
|
|
||||||
Context->Init(Graph, FoundNode, Pin, false);
|
|
||||||
|
|
||||||
// Print header.
|
|
||||||
if (Pin)
|
|
||||||
UMCPServer::Printf(TEXT("\nPin: %s\n"), *MCPUtils::FormatName(Pin));
|
|
||||||
else
|
|
||||||
UMCPServer::Printf(TEXT("Node: %s\n"), *MCPUtils::FormatName(FoundNode));
|
|
||||||
|
|
||||||
// Gather and print node-level actions.
|
|
||||||
UToolMenu* NodeMenu = NewObject<UToolMenu>();
|
|
||||||
FoundNode->GetNodeContextMenuActions(NodeMenu, Context);
|
|
||||||
PrintMenu(NodeMenu, TEXT("Node Actions"), MenuContext);
|
|
||||||
|
|
||||||
// Gather and print schema-level actions.
|
|
||||||
UToolMenu* SchemaMenu = NewObject<UToolMenu>();
|
|
||||||
Schema->GetContextMenuActions(SchemaMenu, Context);
|
|
||||||
PrintMenu(SchemaMenu, TEXT("Schema Actions"), MenuContext);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
#include "MCPFetcher.h"
|
#include "MCPFetcher.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "BlueprintExporter.h"
|
#include "GraphExport.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
#include "Materials/Material.h"
|
#include "Materials/Material.h"
|
||||||
@@ -40,7 +40,7 @@ public:
|
|||||||
UEdGraph *G = F.Walk(Graph).ToGraph().Cast<UEdGraph>();
|
UEdGraph *G = F.Walk(Graph).ToGraph().Cast<UEdGraph>();
|
||||||
if (!G) return;
|
if (!G) return;
|
||||||
|
|
||||||
FlxBlueprintExporter Exporter(G);
|
MCPGraphExport Exporter(G);
|
||||||
UMCPServer::Print(*Exporter.GetOutput());
|
UMCPServer::Print(*Exporter.GetOutput());
|
||||||
if (bDetails)
|
if (bDetails)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#include "BlueprintExporter.h"
|
#include "GraphExport.h"
|
||||||
#include "MCPTypes.h"
|
#include "MCPTypes.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
#include "K2Node_FunctionEntry.h"
|
#include "K2Node_FunctionEntry.h"
|
||||||
#include "MaterialGraph/MaterialGraphNode.h"
|
#include "MaterialGraph/MaterialGraphNode.h"
|
||||||
|
|
||||||
FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph)
|
MCPGraphExport::MCPGraphExport(UEdGraph* InGraph)
|
||||||
: Graph(InGraph)
|
: Graph(InGraph)
|
||||||
{
|
{
|
||||||
SortNodes();
|
SortNodes();
|
||||||
@@ -23,13 +23,24 @@ FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph)
|
|||||||
EmitComments();
|
EmitComments();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MCPGraphExport::MCPGraphExport(UEdGraphNode* InNode)
|
||||||
|
: Graph(InNode->GetGraph())
|
||||||
|
{
|
||||||
|
SortedNodes.Add(InNode);
|
||||||
|
Visited.Add(InNode);
|
||||||
|
EmitLocalVariables();
|
||||||
|
EmitDetails();
|
||||||
|
EmitGraph();
|
||||||
|
EmitComments();
|
||||||
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// General utilities for manipulating UEdGraph nodes.
|
// General utilities for manipulating UEdGraph nodes.
|
||||||
//
|
//
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
UEdGraphPin* FlxBlueprintExporter::GetLinkedTo(UEdGraphPin* Pin)
|
UEdGraphPin* MCPGraphExport::GetLinkedTo(UEdGraphPin* Pin)
|
||||||
{
|
{
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -41,7 +52,7 @@ UEdGraphPin* FlxBlueprintExporter::GetLinkedTo(UEdGraphPin* Pin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FlxBlueprintExporter::IsDefaultToSelf(UEdGraphPin* Pin)
|
bool MCPGraphExport::IsDefaultToSelf(UEdGraphPin* Pin)
|
||||||
{
|
{
|
||||||
// Only valid call-function nodes can have default-to-self.
|
// Only valid call-function nodes can have default-to-self.
|
||||||
UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Pin->GetOwningNode());
|
UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Pin->GetOwningNode());
|
||||||
@@ -60,7 +71,7 @@ bool FlxBlueprintExporter::IsDefaultToSelf(UEdGraphPin* Pin)
|
|||||||
return Pin->PinName.ToString() == DefaultToSelfPinName;
|
return Pin->PinName.ToString() == DefaultToSelfPinName;
|
||||||
}
|
}
|
||||||
|
|
||||||
TArray<UEdGraphPin*> FlxBlueprintExporter::FilterPins(UEdGraphNode* Node, EEdGraphPinDirection Direction, FName Category)
|
TArray<UEdGraphPin*> MCPGraphExport::FilterPins(UEdGraphNode* Node, EEdGraphPinDirection Direction, FName Category)
|
||||||
{
|
{
|
||||||
TArray<UEdGraphPin*> Result;
|
TArray<UEdGraphPin*> Result;
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
for (UEdGraphPin* Pin : Node->Pins)
|
||||||
@@ -72,12 +83,12 @@ TArray<UEdGraphPin*> FlxBlueprintExporter::FilterPins(UEdGraphNode* Node, EEdGra
|
|||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FlxBlueprintExporter::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
bool MCPGraphExport::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
||||||
{
|
{
|
||||||
return FilterPins(Node, Direction, UEdGraphSchema_K2::PC_Exec).Num() > 0;
|
return FilterPins(Node, Direction, UEdGraphSchema_K2::PC_Exec).Num() > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
UEdGraphPin* FlxBlueprintExporter::FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
UEdGraphPin* MCPGraphExport::FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
||||||
{
|
{
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
for (UEdGraphPin* Pin : Node->Pins)
|
||||||
{
|
{
|
||||||
@@ -93,7 +104,7 @@ UEdGraphPin* FlxBlueprintExporter::FindFirstPin(UEdGraphNode* Node, EEdGraphPinD
|
|||||||
//
|
//
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
FString MCPGraphExport::FormatPinSource(UEdGraphPin* Pin)
|
||||||
{
|
{
|
||||||
// If connected, show source node.pin
|
// If connected, show source node.pin
|
||||||
UEdGraphPin* LinkedTo = GetLinkedTo(Pin);
|
UEdGraphPin* LinkedTo = GetLinkedTo(Pin);
|
||||||
@@ -144,7 +155,7 @@ FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::Traverse(UEdGraphNode* Node)
|
void MCPGraphExport::Traverse(UEdGraphNode* Node)
|
||||||
{
|
{
|
||||||
if (Visited.Contains(Node)) return;
|
if (Visited.Contains(Node)) return;
|
||||||
Visited.Add(Node);
|
Visited.Add(Node);
|
||||||
@@ -165,7 +176,7 @@ void FlxBlueprintExporter::Traverse(UEdGraphNode* Node)
|
|||||||
Traverse(LinkedPin->GetOwningNode());
|
Traverse(LinkedPin->GetOwningNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::SortNodes()
|
void MCPGraphExport::SortNodes()
|
||||||
{
|
{
|
||||||
// Find starter nodes: have exec output but no exec input.
|
// Find starter nodes: have exec output but no exec input.
|
||||||
TArray<UEdGraphNode*> Starters;
|
TArray<UEdGraphNode*> Starters;
|
||||||
@@ -196,7 +207,7 @@ void FlxBlueprintExporter::SortNodes()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
void MCPGraphExport::EmitNode(UEdGraphNode* Node)
|
||||||
{
|
{
|
||||||
if (Node->IsA<UEdGraphNode_Comment>()) return;
|
if (Node->IsA<UEdGraphNode_Comment>()) return;
|
||||||
|
|
||||||
@@ -247,7 +258,7 @@ void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitMaterialProperty(UMaterialExpression* Expression, FProperty* Prop, FStringBuilderBase& Out)
|
void MCPGraphExport::EmitMaterialProperty(UMaterialExpression* Expression, FProperty* Prop, FStringBuilderBase& Out)
|
||||||
{
|
{
|
||||||
FString ValueStr = MCPUtils::GetPropertyValueText(Expression, Prop);
|
FString ValueStr = MCPUtils::GetPropertyValueText(Expression, Prop);
|
||||||
ValueStr.ReplaceInline(TEXT("\r\n"), TEXT(" "));
|
ValueStr.ReplaceInline(TEXT("\r\n"), TEXT(" "));
|
||||||
@@ -263,7 +274,7 @@ void FlxBlueprintExporter::EmitMaterialProperty(UMaterialExpression* Expression,
|
|||||||
*ValueStr);
|
*ValueStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary)
|
void MCPGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary)
|
||||||
{
|
{
|
||||||
UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node);
|
UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node);
|
||||||
if (!MatNode || !MatNode->MaterialExpression) return;
|
if (!MatNode || !MatNode->MaterialExpression) return;
|
||||||
@@ -280,7 +291,7 @@ void FlxBlueprintExporter::EmitMaterialProperties(UEdGraphNode* Node, FStringBui
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitLocalVariables()
|
void MCPGraphExport::EmitLocalVariables()
|
||||||
{
|
{
|
||||||
for (UEdGraphNode* Node : Graph->Nodes)
|
for (UEdGraphNode* Node : Graph->Nodes)
|
||||||
{
|
{
|
||||||
@@ -299,7 +310,7 @@ void FlxBlueprintExporter::EmitLocalVariables()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitGraph()
|
void MCPGraphExport::EmitGraph()
|
||||||
{
|
{
|
||||||
for (UEdGraphNode* Node : SortedNodes)
|
for (UEdGraphNode* Node : SortedNodes)
|
||||||
{
|
{
|
||||||
@@ -310,7 +321,7 @@ void FlxBlueprintExporter::EmitGraph()
|
|||||||
Output.Append(TEXT("\n"));
|
Output.Append(TEXT("\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitDetails()
|
void MCPGraphExport::EmitDetails()
|
||||||
{
|
{
|
||||||
for (UEdGraphNode* Node : SortedNodes)
|
for (UEdGraphNode* Node : SortedNodes)
|
||||||
{
|
{
|
||||||
@@ -324,7 +335,7 @@ void FlxBlueprintExporter::EmitDetails()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitComments()
|
void MCPGraphExport::EmitComments()
|
||||||
{
|
{
|
||||||
for (UEdGraphNode* CommentNode : SortedNodes)
|
for (UEdGraphNode* CommentNode : SortedNodes)
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,10 @@
|
|||||||
#include "ToolMenuEntry.h"
|
#include "ToolMenuEntry.h"
|
||||||
#include "ToolMenuDelegates.h"
|
#include "ToolMenuDelegates.h"
|
||||||
#include "ToolMenuContext.h"
|
#include "ToolMenuContext.h"
|
||||||
#include "BlueprintEditor.h"
|
#include "ToolMenus.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
|
#include "EdGraph/EdGraphSchema.h"
|
||||||
|
#include "EdGraphSchema_K2.h"
|
||||||
#include "Framework/Commands/UIAction.h"
|
#include "Framework/Commands/UIAction.h"
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -27,66 +30,207 @@ struct MCPPrivateAccessor
|
|||||||
|
|
||||||
// ----- FToolMenuEntry::Action -----
|
// ----- FToolMenuEntry::Action -----
|
||||||
|
|
||||||
struct FToolMenuEntry_Action_Tag
|
struct Tag_FToolMenuEntry_Action
|
||||||
{
|
{
|
||||||
using type = FToolUIActionChoice FToolMenuEntry::*;
|
using type = FToolUIActionChoice FToolMenuEntry::*;
|
||||||
friend type GetPtr(FToolMenuEntry_Action_Tag);
|
friend type GetPtr(Tag_FToolMenuEntry_Action);
|
||||||
};
|
};
|
||||||
template struct MCPPrivateAccessor<FToolMenuEntry_Action_Tag, &FToolMenuEntry::Action>;
|
template struct MCPPrivateAccessor<Tag_FToolMenuEntry_Action, &FToolMenuEntry::Action>;
|
||||||
|
|
||||||
|
static const FToolUIActionChoice& GetAction(const FToolMenuEntry& Entry)
|
||||||
|
{
|
||||||
|
return Entry.*GetPtr(Tag_FToolMenuEntry_Action());
|
||||||
|
}
|
||||||
|
|
||||||
// ----- FToolMenuEntry::Command -----
|
// ----- FToolMenuEntry::Command -----
|
||||||
|
|
||||||
struct FToolMenuEntry_Command_Tag
|
struct Tag_FToolMenuEntry_Command
|
||||||
{
|
{
|
||||||
using type = TSharedPtr<const FUICommandInfo> FToolMenuEntry::*;
|
using type = TSharedPtr<const FUICommandInfo> FToolMenuEntry::*;
|
||||||
friend type GetPtr(FToolMenuEntry_Command_Tag);
|
friend type GetPtr(Tag_FToolMenuEntry_Command);
|
||||||
};
|
};
|
||||||
template struct MCPPrivateAccessor<FToolMenuEntry_Command_Tag, &FToolMenuEntry::Command>;
|
template struct MCPPrivateAccessor<Tag_FToolMenuEntry_Command, &FToolMenuEntry::Command>;
|
||||||
|
|
||||||
// ----- FBlueprintEditor::GraphEditorCommands -----
|
static bool HasCommand(const FToolMenuEntry& Entry)
|
||||||
|
|
||||||
struct FBlueprintEditor_Commands_Tag
|
|
||||||
{
|
{
|
||||||
using type = TSharedPtr<FUICommandList> FBlueprintEditor::*;
|
return Entry.*GetPtr(Tag_FToolMenuEntry_Command()) != nullptr;
|
||||||
friend type GetPtr(FBlueprintEditor_Commands_Tag);
|
|
||||||
};
|
|
||||||
template struct MCPPrivateAccessor<FBlueprintEditor_Commands_Tag, &FBlueprintEditor::GraphEditorCommands>;
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// Helpers to access private fields of a FToolMenuEntry.
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
static const FToolUIActionChoice& GetChoice(const FToolMenuEntry& Entry)
|
|
||||||
{
|
|
||||||
return Entry.*GetPtr(FToolMenuEntry_Action_Tag());
|
|
||||||
}
|
|
||||||
|
|
||||||
static const TSharedPtr<const FUICommandInfo>& GetCommand(const FToolMenuEntry& Entry)
|
|
||||||
{
|
|
||||||
return Entry.*GetPtr(FToolMenuEntry_Command_Tag());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Resolve a menu entry's callback.
|
// Given a menu entry label, and a pin name (possibly empty),
|
||||||
|
// generate a better label for an LLM to type. The goal here
|
||||||
|
// is to have consistent spacing, consistent casing, so that
|
||||||
|
// the LLM can easily remember what to type.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
FText MCPToolMenu::MakeBetterLabel(const UEdGraphPin *Pin, const FText &EntryLabel)
|
||||||
|
{
|
||||||
|
FString Sanitized = EntryLabel.ToString();
|
||||||
|
int32 Dst = 0;
|
||||||
|
bool Upper = true;
|
||||||
|
for (int32 Src = 0; Src < Sanitized.Len(); Src++)
|
||||||
|
{
|
||||||
|
TCHAR c = Sanitized[Src];
|
||||||
|
if (FChar::IsAlnum(c))
|
||||||
|
{
|
||||||
|
if (Upper) c = FChar::ToUpper(c);
|
||||||
|
Sanitized[Dst++] = c;
|
||||||
|
Upper = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Upper = true;
|
||||||
|
if ((c <= 0x20)||(c == 0x7F)) continue;
|
||||||
|
if (c == ':') c = '-';
|
||||||
|
Sanitized[Dst++] = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Sanitized.LeftInline(Dst);
|
||||||
|
if (Pin)
|
||||||
|
{
|
||||||
|
Sanitized = FString::Printf(TEXT("Pin:%s:%s"), *MCPUtils::FormatName(Pin), *Sanitized);
|
||||||
|
}
|
||||||
|
return FText::FromString(Sanitized);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Check if an array of entries contains a specific label.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
bool MCPToolMenu::ContainsText(const TArray<FText> &Texts, const FText &Value)
|
||||||
|
{
|
||||||
|
for (const FText &Text : Texts)
|
||||||
|
{
|
||||||
|
if (Value.IdenticalTo(Text))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// AddEntry — create a synthetic menu entry with a direct action.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
void MCPToolMenu::AddEntry(TArray<FToolMenuEntry>& Entries, UEdGraphPin* Pin,
|
||||||
|
const TCHAR* Label, FCanExecuteAction CanExec, FExecuteAction Exec)
|
||||||
|
{
|
||||||
|
if (!CanExec.Execute())
|
||||||
|
return;
|
||||||
|
FToolMenuEntry Entry = FToolMenuEntry::InitMenuEntry(
|
||||||
|
NAME_None,
|
||||||
|
MakeBetterLabel(Pin, FText::FromString(Label)),
|
||||||
|
FText::GetEmpty(),
|
||||||
|
FSlateIcon(),
|
||||||
|
FUIAction(MoveTemp(Exec), MoveTemp(CanExec)));
|
||||||
|
Entries.Add(MoveTemp(Entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
void MCPToolMenu::AddSyntheticEntries(TArray<FToolMenuEntry> &Entries, UEdGraphNode *NodePtr)
|
||||||
|
{
|
||||||
|
const UEdGraphSchema_K2 *K2Schema = Cast<UEdGraphSchema_K2>(NodePtr->GetSchema());
|
||||||
|
if (K2Schema == nullptr) return;
|
||||||
|
// TWeakObjectPtr<UEdGraphNode> Node(NodePtr);
|
||||||
|
for (UEdGraphPin *PinPtr : NodePtr->Pins)
|
||||||
|
{
|
||||||
|
if (PinPtr->bHidden) continue;
|
||||||
|
FEdGraphPinReference Pin(PinPtr);
|
||||||
|
AddEntry(Entries, PinPtr, TEXT("SplitStructPin"),
|
||||||
|
[=](){ UEdGraphPin *P=Pin.Get(); return P && K2Schema->CanSplitStructPin(*P); },
|
||||||
|
[=](){ UEdGraphPin *P=Pin.Get(); if (P) K2Schema->SplitPin(P); });
|
||||||
|
AddEntry(Entries, PinPtr, TEXT("RecombineStructPin"),
|
||||||
|
[=](){ UEdGraphPin *P=Pin.Get(); return P && K2Schema->CanRecombineStructPin(*P); },
|
||||||
|
[=](){ UEdGraphPin *P=Pin.Get(); if (P) K2Schema->RecombinePin(P); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Get the Menu Items for a given node and pin. This doesn't
|
||||||
|
// do anything with the labels yet.
|
||||||
|
// ============================================================
|
||||||
|
|
||||||
|
TArray<FToolMenuEntry> MCPToolMenu::GetMenuItems(
|
||||||
|
UGraphNodeContextMenuContext* GNC, const FToolMenuContext &TMC)
|
||||||
|
{
|
||||||
|
TArray<FToolMenuEntry> Result;
|
||||||
|
UToolMenu* Menu = NewObject<UToolMenu>();
|
||||||
|
GNC->Node->GetNodeContextMenuActions(Menu, GNC);
|
||||||
|
//GNC->Node->GetSchema()->GetContextMenuActions(Menu, GNC);
|
||||||
|
for (FToolMenuSection& Section : Menu->Sections)
|
||||||
|
{
|
||||||
|
Result.Append(Section.Blocks);
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FToolMenuEntry> MCPToolMenu::GetMenuItems(UEdGraphNode *Node, const FToolMenuContext &Context)
|
||||||
|
{
|
||||||
|
// Create the two context objects.
|
||||||
|
TArray<FToolMenuEntry> Result;
|
||||||
|
UGraphNodeContextMenuContext* GNC = NewObject<UGraphNodeContextMenuContext>();
|
||||||
|
|
||||||
|
// Fetch the menu items for the node.
|
||||||
|
GNC->Init(Node->GetGraph(), Node, nullptr, false);
|
||||||
|
TArray<FToolMenuEntry> NodeEntries = GetMenuItems(GNC, Context);
|
||||||
|
|
||||||
|
// Improve the labels for the node entries, and also
|
||||||
|
// record the original labels.
|
||||||
|
TArray<FText> OriginalLabels;
|
||||||
|
for (FToolMenuEntry &Entry: NodeEntries)
|
||||||
|
{
|
||||||
|
FText Label = Entry.Label.Get();
|
||||||
|
OriginalLabels.Add(Label);
|
||||||
|
if (!CanExecute(Entry, Context)) continue;
|
||||||
|
Entry.Label = MakeBetterLabel(nullptr, Label);
|
||||||
|
Result.Add(Entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch the Menu items for the pins. Discard
|
||||||
|
// pins whose original label exactly matches the
|
||||||
|
// original label of a node entry.
|
||||||
|
for (const UEdGraphPin *Pin : Node->Pins)
|
||||||
|
{
|
||||||
|
if (Pin->bHidden) continue;
|
||||||
|
FString PinName = MCPUtils::FormatName(Pin);
|
||||||
|
GNC->Init(Node->GetGraph(), Node, Pin, false);
|
||||||
|
TArray<FToolMenuEntry> PinEntries = GetMenuItems(GNC, Context);
|
||||||
|
for (FToolMenuEntry &PinEntry : PinEntries)
|
||||||
|
{
|
||||||
|
FText Label = PinEntry.Label.Get();
|
||||||
|
if (!ContainsText(OriginalLabels, Label))
|
||||||
|
{
|
||||||
|
if (CanExecute(PinEntry, Context))
|
||||||
|
{
|
||||||
|
PinEntry.Label = MakeBetterLabel(Pin, Label);
|
||||||
|
Result.Add(PinEntry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AddSyntheticEntries(Result, Node);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Menu entry resolution
|
||||||
//
|
//
|
||||||
// There are four callback mechanisms we handle:
|
// We only handle the three Action-based callback mechanisms:
|
||||||
// 1. Command — looked up in the context's command list
|
// 1. FToolUIAction — delegates that take a FToolMenuContext
|
||||||
// 2. FToolUIAction — delegates that take a FToolMenuContext
|
// 2. FToolDynamicUIAction — same, Blueprint-friendly variant
|
||||||
// 3. FToolDynamicUIAction — same, Blueprint-friendly variant
|
// 3. FUIAction — plain delegates, no context needed
|
||||||
// 4. FUIAction — plain delegates, no context needed
|
//
|
||||||
|
// Command-based entries are skipped — they depend on editor
|
||||||
|
// selection/focus state which we can't reliably provide.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|
||||||
bool MCPToolMenu::CanExecute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
|
bool MCPToolMenu::CanExecute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
|
||||||
{
|
{
|
||||||
const TSharedPtr<const FUICommandInfo>& Command = GetCommand(Entry);
|
if (HasCommand(Entry))
|
||||||
if (Command.IsValid())
|
return false;
|
||||||
{
|
|
||||||
TSharedPtr<const FUICommandList> OutCommandList;
|
|
||||||
const FUIAction* Found = Entry.GetActionForCommand(Context, OutCommandList);
|
|
||||||
return Found && Found->IsBound() && Found->CanExecute();
|
|
||||||
}
|
|
||||||
|
|
||||||
const FToolUIActionChoice& Choice = GetChoice(Entry);
|
const FToolUIActionChoice& Choice = GetAction(Entry);
|
||||||
|
|
||||||
if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
|
if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
|
||||||
{
|
{
|
||||||
@@ -110,20 +254,10 @@ bool MCPToolMenu::CanExecute(const FToolMenuEntry& Entry, const FToolMenuContext
|
|||||||
|
|
||||||
bool MCPToolMenu::Execute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
|
bool MCPToolMenu::Execute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
|
||||||
{
|
{
|
||||||
if (!CanExecute(Entry, Context))
|
if (HasCommand(Entry))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
const TSharedPtr<const FUICommandInfo>& Command = GetCommand(Entry);
|
const FToolUIActionChoice& Choice = GetAction(Entry);
|
||||||
if (Command.IsValid())
|
|
||||||
{
|
|
||||||
TSharedPtr<const FUICommandList> OutCommandList;
|
|
||||||
const FUIAction* Found = Entry.GetActionForCommand(Context, OutCommandList);
|
|
||||||
if (Found)
|
|
||||||
return Found->Execute();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FToolUIActionChoice& Choice = GetChoice(Entry);
|
|
||||||
|
|
||||||
if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
|
if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
|
||||||
{
|
{
|
||||||
@@ -142,8 +276,3 @@ bool MCPToolMenu::Execute(const FToolMenuEntry& Entry, const FToolMenuContext& C
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TSharedPtr<FUICommandList>& MCPToolMenu::GetGraphEditorCommands(const FBlueprintEditor& Editor)
|
|
||||||
{
|
|
||||||
return Editor.*GetPtr(FBlueprintEditor_Commands_Tag());
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
|
|
||||||
class UMaterialExpression;
|
class UMaterialExpression;
|
||||||
|
|
||||||
class FlxBlueprintExporter
|
class MCPGraphExport
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
FlxBlueprintExporter(UEdGraph* InGraph);
|
MCPGraphExport(UEdGraph* InGraph);
|
||||||
|
MCPGraphExport(UEdGraphNode* InNode);
|
||||||
|
|
||||||
const FString GetOutput() { return Output.ToString(); }
|
const FString GetOutput() { return Output.ToString(); }
|
||||||
const FString GetDetails() { return Details.ToString(); }
|
const FString GetDetails() { return Details.ToString(); }
|
||||||
@@ -1,10 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "Framework/Commands/UIAction.h"
|
||||||
|
|
||||||
struct FToolMenuEntry;
|
struct FToolMenuEntry;
|
||||||
struct FToolMenuContext;
|
struct FToolMenuContext;
|
||||||
class FBlueprintEditor;
|
class UEdGraphNode;
|
||||||
|
class UEdGraphPin;
|
||||||
|
class UGraphNodeContextMenuContext;
|
||||||
|
|
||||||
// Utilities for manipulating UToolMenu structures.
|
// Utilities for manipulating UToolMenu structures.
|
||||||
// Uses the C++ template explicit-instantiation loophole to
|
// Uses the C++ template explicit-instantiation loophole to
|
||||||
@@ -12,13 +15,41 @@ class FBlueprintEditor;
|
|||||||
class MCPToolMenu
|
class MCPToolMenu
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// Get the menu items for a given Node. This includes
|
||||||
|
// the menu items for the pins. The labels are updated to be
|
||||||
|
// nice labels for an LLM. Only includes action-based entries;
|
||||||
|
// command-based entries (which depend on editor selection/focus
|
||||||
|
// state) are excluded.
|
||||||
|
static TArray<FToolMenuEntry> GetMenuItems(UEdGraphNode *Node, const FToolMenuContext &TMContext);
|
||||||
|
|
||||||
// Resolve a menu entry to an executable action and check if it can execute.
|
// Resolve a menu entry to an executable action and check if it can execute.
|
||||||
static bool CanExecute(const FToolMenuEntry& Entry, const FToolMenuContext& Context);
|
// Entries that use Command-based callbacks are always false.
|
||||||
|
static bool CanExecute(const FToolMenuEntry& Entry, const FToolMenuContext& TMContext);
|
||||||
|
|
||||||
// Resolve a menu entry to an executable action and execute it.
|
// Resolve a menu entry to an executable action and execute it.
|
||||||
// Returns false if the action is not active or has no bound delegate.
|
// Returns false if the action is not active or has no bound delegate.
|
||||||
static bool Execute(const FToolMenuEntry& Entry, const FToolMenuContext& Context);
|
// Entries that use Command-based callbacks are always false.
|
||||||
|
static bool Execute(const FToolMenuEntry& Entry, const FToolMenuContext& TMContext);
|
||||||
|
|
||||||
// Get the GraphEditorCommands from a blueprint editor (private field).
|
|
||||||
static const TSharedPtr<FUICommandList>& GetGraphEditorCommands(const FBlueprintEditor& Editor);
|
private:
|
||||||
|
// Add a synthetic menu entry. Calls CanExec immediately; if false,
|
||||||
|
// the entry is not added. Both lambdas are stored in the FUIAction.
|
||||||
|
static void AddEntry(TArray<FToolMenuEntry>& Entries, UEdGraphPin* Pin,
|
||||||
|
const TCHAR* Label, FCanExecuteAction CanExec, FExecuteAction Exec);
|
||||||
|
|
||||||
|
template<typename FC, typename FE>
|
||||||
|
static void AddEntry(TArray<FToolMenuEntry>& Entries, UEdGraphPin* Pin,
|
||||||
|
const TCHAR* Label, FC&& CanExec, FE&& Exec)
|
||||||
|
{
|
||||||
|
AddEntry(Entries, Pin, Label,
|
||||||
|
FCanExecuteAction::CreateLambda(Forward<FC>(CanExec)),
|
||||||
|
FExecuteAction::CreateLambda(Forward<FE>(Exec)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void AddSyntheticEntries(TArray<FToolMenuEntry> &Entries, UEdGraphNode *Node);
|
||||||
|
static FText MakeBetterLabel(const UEdGraphPin *Pin, const FText &EntryLabel);
|
||||||
|
static bool ContainsText(const TArray<FText> &Texts, const FText &Value);
|
||||||
|
static TArray<FToolMenuEntry> GetMenuItems(
|
||||||
|
UGraphNodeContextMenuContext *GNC, const FToolMenuContext &TMC);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ public:
|
|||||||
|
|
||||||
////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
static void SanitizeNameInPlace(FString& Name);
|
||||||
static FString FormatNodeTitle(const UEdGraphNode *Node);
|
static FString FormatNodeTitle(const UEdGraphNode *Node);
|
||||||
|
|
||||||
// ----- Enum helpers -----
|
// ----- Enum helpers -----
|
||||||
@@ -174,7 +175,6 @@ public:
|
|||||||
static void FormatCommandHelp(UClass* HandlerClass);
|
static void FormatCommandHelp(UClass* HandlerClass);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void SanitizeNameInPlace(FString& Name);
|
|
||||||
static void AppendNumericSuffix(FString &Name, int32 N);
|
static void AppendNumericSuffix(FString &Name, int32 N);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user