Files
unreal-engine-mcp-system-pl…/Source/UEBlueprintMCPEditor/Private/MCPGraphLibrary.cpp
Bonchellon eba71c4ca8 Unified, portable Unreal Engine MCP system plugin
Merge of the two project copies into one self-contained plugin (the superset:
variable_op + variables.py, full pcg_op runtime/declarative/preset ops, the
CreateWidgetFloatAnimation widget tool, and full Voxel graph authoring).

Made fully project- and machine-agnostic — no hardcoded paths:
- New src/projectPaths.js auto-detects the host .uproject (walk-up), project
  name, Editor build target, log file, and engine install (EngineAssociation
  via launcher manifest/registry, else installed-engine scan). All overridable
  via UE_* env vars.
- Rewired buildOrchestrator/insights/launcher/insightsExporter/server.js and the
  Python workers (cpp_scaffold, live_coding, apply_graph, console) off the old
  C:/Github/ihy, IHY*, E:/UE_Versions and UE_5.7 literals onto the resolver.
- Voxel made optional: Build.cs auto-detects the Voxel plugin (env
  UE_MCP_WITH_VOXEL override) and the C++ compiles to stubs under WITH_MCP_VOXEL,
  so the module builds in projects without Voxel; .uplugin marks Voxel optional.
- De-branded the agent-gateway and docs; scrubbed a leaked API key; excluded
  node_modules/Binaries/Intermediate/__pycache__/secrets from the repo.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 01:07:24 +03:00

2373 lines
91 KiB
C++

#include "MCPGraphLibrary.h"
#include "Engine/Blueprint.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraphSchema_K2.h"
#include "EdGraphSchema_K2_Actions.h"
// K2Nodes
#include "K2Node_CallFunction.h"
#include "K2Node_Event.h"
#include "K2Node_CustomEvent.h"
#include "K2Node_ComponentBoundEvent.h"
#include "K2Node_IfThenElse.h"
#include "K2Node_ExecutionSequence.h"
#include "K2Node_VariableGet.h"
#include "K2Node_VariableSet.h"
#include "K2Node_Self.h"
#include "K2Node_DynamicCast.h"
#include "K2Node_SwitchInteger.h"
#include "K2Node_SwitchString.h"
#include "K2Node_SwitchEnum.h"
#include "K2Node_MakeArray.h"
#include "K2Node_MakeStruct.h"
#include "K2Node_BreakStruct.h"
#include "K2Node_SetFieldsInStruct.h"
#include "K2Node_MacroInstance.h"
#include "K2Node_SpawnActorFromClass.h"
#include "K2Node_FormatText.h"
#include "K2Node_GetSubsystem.h"
#include "K2Node_LoadAsset.h"
#include "K2Node_AddDelegate.h"
#include "K2Node_RemoveDelegate.h"
#include "K2Node_CallDelegate.h"
#include "K2Node_AssignDelegate.h"
#include "K2Node_Knot.h"
#include "K2Node_GetArrayItem.h"
#include "K2Node_EnumLiteral.h"
#include "K2Node_GetClassDefaults.h"
#include "K2Node_FunctionResult.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_FunctionTerminator.h"
#include "K2Node_Switch.h"
#include "EdGraphNode_Comment.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Editor.h"
#include "BlueprintEditor.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "WidgetBlueprint.h"
#include "WidgetBlueprintEditorUtils.h"
#include "Blueprint/WidgetTree.h"
#include "Kismet/KismetSystemLibrary.h"
#include "KismetCompilerMisc.h"
#include "KismetCompiler.h"
#include "Components/ActorComponent.h"
#include "Components/BoxComponent.h"
#include "Components/CanvasPanelSlot.h"
#include "Components/NamedSlotInterface.h"
#include "Components/PanelSlot.h"
#include "Components/PanelWidget.h"
#include "Components/SceneComponent.h"
#include "Components/Widget.h"
#include "Layout/Margin.h"
#include "UObject/UnrealType.h"
#include "Logging/TokenizedMessage.h"
#include "Misc/UObjectToken.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonWriter.h"
#include "Dom/JsonObject.h"
#include "Dom/JsonValue.h"
// UMG WidgetAnimation authoring
#include "Animation/WidgetAnimation.h"
#include "MovieScene.h"
#include "Tracks/MovieSceneFloatTrack.h"
#include "Sections/MovieSceneFloatSection.h"
#include "Channels/MovieSceneFloatChannel.h"
#include "Channels/MovieSceneChannelProxy.h"
// ---------------------------------------------------------------------------
// Generic spawn helper
// ---------------------------------------------------------------------------
template <typename TNode>
TNode* UMCPGraphLibrary::PlaceNode(UEdGraph* Graph, FVector2D Position)
{
if (!Graph) return nullptr;
TNode* Node = NewObject<TNode>(Graph);
Node->CreateNewGuid();
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, /*bUserAction*/ true, /*bSelectNewNode*/ false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
return Node;
}
static void MarkBPDirty(UBlueprint* BP)
{
if (BP) FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
}
static FString MCPWidgetJsonStringify(const TSharedRef<FJsonObject>& Object)
{
FString Out;
const TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Out);
FJsonSerializer::Serialize(Object, Writer);
return Out;
}
static UWidgetTree* MCPGetWidgetTree(UWidgetBlueprint* BP)
{
return BP ? BP->WidgetTree : nullptr;
}
static UWidget* MCPFindWidget(UWidgetBlueprint* BP, FName WidgetName)
{
UWidgetTree* Tree = MCPGetWidgetTree(BP);
if (!Tree) return nullptr;
if (WidgetName.IsNone() || WidgetName == FName(TEXT("Root")) || WidgetName == FName(TEXT("root")))
{
return Tree->RootWidget;
}
return Tree->FindWidget(WidgetName);
}
static void MarkWidgetBPDirty(UWidgetBlueprint* BP, bool bStructurallyModified = true)
{
if (!BP) return;
BP->Modify();
if (BP->WidgetTree)
{
BP->WidgetTree->SetFlags(RF_Transactional);
BP->WidgetTree->Modify();
}
if (bStructurallyModified)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
}
else
{
FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
}
}
static bool SetObjectPropertyFromString(UObject* Object, FName PropertyName, const FString& Value)
{
if (!Object || PropertyName.IsNone()) return false;
FProperty* Property = FindFProperty<FProperty>(Object->GetClass(), PropertyName);
if (!Property) return false;
Object->SetFlags(RF_Transactional);
Object->Modify();
void* PropertyValue = Property->ContainerPtrToValuePtr<void>(Object);
const TCHAR* Buffer = *Value;
if (!Property->ImportText_Direct(Buffer, PropertyValue, Object, PPF_None))
{
return false;
}
#if WITH_EDITOR
FPropertyChangedEvent Event(Property);
Object->PostEditChangeProperty(Event);
#endif
return true;
}
static TSharedRef<FJsonObject> ExportObjectPropertiesJson(UObject* Object)
{
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
if (!Object)
{
Root->SetBoolField(TEXT("ok"), false);
Root->SetStringField(TEXT("error"), TEXT("null object"));
return Root;
}
Root->SetBoolField(TEXT("ok"), true);
Root->SetStringField(TEXT("class"), Object->GetClass()->GetPathName());
TMap<FName, FString> Props;
FWidgetBlueprintEditorUtils::ExportPropertiesToText(Object, Props);
TSharedRef<FJsonObject> PropJson = MakeShared<FJsonObject>();
for (const TPair<FName, FString>& Pair : Props)
{
PropJson->SetStringField(Pair.Key.ToString(), Pair.Value);
}
Root->SetObjectField(TEXT("properties"), PropJson);
return Root;
}
static TSharedRef<FJsonObject> WidgetSlotJson(UWidget* Widget)
{
TSharedRef<FJsonObject> SlotJson = MakeShared<FJsonObject>();
if (!Widget || !Widget->Slot)
{
SlotJson->SetBoolField(TEXT("has_slot"), false);
return SlotJson;
}
UPanelSlot* Slot = Widget->Slot;
SlotJson->SetBoolField(TEXT("has_slot"), true);
SlotJson->SetStringField(TEXT("class"), Slot->GetClass()->GetPathName());
if (Slot->Parent)
{
SlotJson->SetStringField(TEXT("parent"), Slot->Parent->GetFName().ToString());
}
if (UCanvasPanelSlot* CanvasSlot = Cast<UCanvasPanelSlot>(Slot))
{
const FVector2D Pos = CanvasSlot->GetPosition();
const FVector2D Size = CanvasSlot->GetSize();
const FVector2D Align = CanvasSlot->GetAlignment();
const FAnchors Anchors = CanvasSlot->GetAnchors();
SlotJson->SetArrayField(TEXT("position"), {
MakeShared<FJsonValueNumber>(Pos.X),
MakeShared<FJsonValueNumber>(Pos.Y)
});
SlotJson->SetArrayField(TEXT("size"), {
MakeShared<FJsonValueNumber>(Size.X),
MakeShared<FJsonValueNumber>(Size.Y)
});
SlotJson->SetArrayField(TEXT("alignment"), {
MakeShared<FJsonValueNumber>(Align.X),
MakeShared<FJsonValueNumber>(Align.Y)
});
SlotJson->SetArrayField(TEXT("anchors_min"), {
MakeShared<FJsonValueNumber>(Anchors.Minimum.X),
MakeShared<FJsonValueNumber>(Anchors.Minimum.Y)
});
SlotJson->SetArrayField(TEXT("anchors_max"), {
MakeShared<FJsonValueNumber>(Anchors.Maximum.X),
MakeShared<FJsonValueNumber>(Anchors.Maximum.Y)
});
SlotJson->SetBoolField(TEXT("auto_size"), CanvasSlot->GetAutoSize());
SlotJson->SetNumberField(TEXT("z_order"), CanvasSlot->GetZOrder());
}
return SlotJson;
}
static TSharedRef<FJsonObject> WidgetToJson(UWidget* Widget)
{
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
if (!Widget)
{
Json->SetBoolField(TEXT("ok"), false);
return Json;
}
Json->SetStringField(TEXT("name"), Widget->GetFName().ToString());
Json->SetStringField(TEXT("class"), Widget->GetClass()->GetPathName());
Json->SetStringField(TEXT("label"), Widget->GetLabelText().ToString());
Json->SetBoolField(TEXT("is_variable"), Widget->bIsVariable);
Json->SetObjectField(TEXT("slot"), WidgetSlotJson(Widget));
TArray<TSharedPtr<FJsonValue>> Children;
if (UPanelWidget* Panel = Cast<UPanelWidget>(Widget))
{
const int32 ChildCount = Panel->GetChildrenCount();
for (int32 Index = 0; Index < ChildCount; ++Index)
{
if (UWidget* Child = Panel->GetChildAt(Index))
{
TSharedRef<FJsonObject> ChildRef = MakeShared<FJsonObject>();
ChildRef->SetStringField(TEXT("name"), Child->GetFName().ToString());
ChildRef->SetStringField(TEXT("class"), Child->GetClass()->GetPathName());
ChildRef->SetNumberField(TEXT("index"), Index);
Children.Add(MakeShared<FJsonValueObject>(ChildRef));
}
}
}
Json->SetArrayField(TEXT("children"), Children);
TArray<FName> SlotNames;
if (INamedSlotInterface* NamedSlots = Cast<INamedSlotInterface>(Widget))
{
NamedSlots->GetSlotNames(SlotNames);
TArray<TSharedPtr<FJsonValue>> NamedSlotValues;
for (FName SlotName : SlotNames)
{
TSharedRef<FJsonObject> Slot = MakeShared<FJsonObject>();
Slot->SetStringField(TEXT("slot"), SlotName.ToString());
if (UWidget* Content = NamedSlots->GetContentForSlot(SlotName))
{
Slot->SetStringField(TEXT("content"), Content->GetFName().ToString());
}
NamedSlotValues.Add(MakeShared<FJsonValueObject>(Slot));
}
Json->SetArrayField(TEXT("named_slots"), NamedSlotValues);
}
return Json;
}
static bool AddExistingWidgetToParent(UWidgetBlueprint* BP, UWidget* Widget, UWidget* Parent, int32 Index)
{
if (!BP || !Widget) return false;
UWidgetTree* Tree = BP->WidgetTree;
if (!Tree) return false;
Widget->SetFlags(RF_Transactional);
Widget->Modify();
if (!Parent)
{
if (Tree->RootWidget && Tree->RootWidget != Widget)
{
return false;
}
Tree->RootWidget = Widget;
return true;
}
if (UPanelWidget* Panel = Cast<UPanelWidget>(Parent))
{
Panel->SetFlags(RF_Transactional);
Panel->Modify();
if (!Panel->CanAddMoreChildren())
{
return false;
}
if (Index >= 0 && Index <= Panel->GetChildrenCount())
{
return Panel->InsertChildAt(Index, Widget) != nullptr;
}
return Panel->AddChild(Widget) != nullptr;
}
return false;
}
// ---------------------------------------------------------------------------
// Graph lookup
// ---------------------------------------------------------------------------
UEdGraph* UMCPGraphLibrary::FindEventGraph(UBlueprint* Blueprint)
{
if (!Blueprint) return nullptr;
for (UEdGraph* G : Blueprint->UbergraphPages) { if (G) return G; }
return nullptr;
}
UEdGraph* UMCPGraphLibrary::FindFunctionGraph(UBlueprint* Blueprint, FName FunctionName)
{
if (!Blueprint) return nullptr;
for (UEdGraph* G : Blueprint->FunctionGraphs) { if (G && G->GetFName() == FunctionName) return G; }
for (UEdGraph* G : Blueprint->UbergraphPages) { if (G && G->GetFName() == FunctionName) return G; }
for (UEdGraph* G : Blueprint->MacroGraphs) { if (G && G->GetFName() == FunctionName) return G; }
return nullptr;
}
TArray<FString> UMCPGraphLibrary::ListNodeIds(UEdGraph* Graph)
{
TArray<FString> Out;
if (!Graph) return Out;
for (UEdGraphNode* N : Graph->Nodes) { if (N) Out.Add(N->NodeGuid.ToString()); }
return Out;
}
TArray<FString> UMCPGraphLibrary::ListPinsOnNode(UEdGraph* Graph, const FString& NodeId)
{
TArray<FString> Out;
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) return Out;
for (UEdGraphPin* P : N->Pins)
{
if (!P) continue;
const TCHAR* Dir = (P->Direction == EGPD_Input) ? TEXT("in") : TEXT("out");
Out.Add(FString::Printf(TEXT("%s|%s|%s"), *P->PinName.ToString(), Dir, *P->PinType.PinCategory.ToString()));
}
return Out;
}
int32 UMCPGraphLibrary::ClearGraph(UEdGraph* Graph)
{
if (!Graph) return 0;
TArray<UEdGraphNode*> Snapshot = Graph->Nodes;
int32 Deleted = 0;
for (UEdGraphNode* N : Snapshot)
{
if (!N) continue;
Graph->RemoveNode(N);
++Deleted;
}
if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph))
{
MarkBPDirty(BP);
}
return Deleted;
}
bool UMCPGraphLibrary::DeleteNode(UEdGraph* Graph, const FString& NodeId)
{
if (!Graph) return false;
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) return false;
Graph->RemoveNode(N);
if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP);
return true;
}
// ---------------------------------------------------------------------------
// Spawners — basic
// ---------------------------------------------------------------------------
FString UMCPGraphLibrary::SpawnCallFunctionNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* FunctionOwnerClass, FName FunctionName, FVector2D Position)
{
if (!Blueprint || !Graph || !FunctionOwnerClass) return FString();
UK2Node_CallFunction* Node = NewObject<UK2Node_CallFunction>(Graph);
Node->CreateNewGuid();
Node->FunctionReference.SetExternalMember(FunctionName, FunctionOwnerClass);
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnEventNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* OwnerClass, FName EventName, FVector2D Position)
{
if (!Blueprint || !Graph) return FString();
UClass* EffectiveOwner = OwnerClass;
if (!EffectiveOwner) EffectiveOwner = Blueprint->ParentClass;
if (!EffectiveOwner) return FString();
UK2Node_Event* Node = NewObject<UK2Node_Event>(Graph);
Node->CreateNewGuid();
Node->EventReference.SetExternalMember(EventName, EffectiveOwner);
Node->bOverrideFunction = true;
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnComponentBoundEvent(UBlueprint* Blueprint, UEdGraph* Graph, FName ComponentName, FName DelegateName, FVector2D Position)
{
if (!Blueprint || !Graph) return FString();
UClass* OwnerClass = Blueprint->GeneratedClass ? Blueprint->GeneratedClass : Blueprint->ParentClass;
if (!OwnerClass) return FString();
FObjectProperty* CompProp = FindFProperty<FObjectProperty>(OwnerClass, ComponentName);
if (!CompProp || !CompProp->PropertyClass) return FString();
FMulticastDelegateProperty* DelegateProp = FindFProperty<FMulticastDelegateProperty>(CompProp->PropertyClass, DelegateName);
if (!DelegateProp) return FString();
UK2Node_ComponentBoundEvent* Node = NewObject<UK2Node_ComponentBoundEvent>(Graph);
Node->CreateNewGuid();
Node->InitializeComponentBoundEventParams(CompProp, DelegateProp);
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnCustomEventNode(UBlueprint* Blueprint, UEdGraph* Graph, FName EventName, FVector2D Position)
{
UK2Node_CustomEvent* Node = PlaceNode<UK2Node_CustomEvent>(Graph, Position);
if (!Node) return FString();
Node->CustomFunctionName = EventName;
Node->ReconstructNode();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
bool UMCPGraphLibrary::SetCustomEventNetworkFlags(UBlueprint* Blueprint, UEdGraph* Graph, const FString& NodeIdOrEventName, FName Mode, bool bReliable)
{
if (!Graph) return false;
UK2Node_CustomEvent* EventNode = Cast<UK2Node_CustomEvent>(FindNodeByGuid(Graph, NodeIdOrEventName));
if (!EventNode)
{
for (UEdGraphNode* Node : Graph->Nodes)
{
UK2Node_CustomEvent* Candidate = Cast<UK2Node_CustomEvent>(Node);
if (Candidate && Candidate->CustomFunctionName.ToString() == NodeIdOrEventName)
{
EventNode = Candidate;
break;
}
}
}
if (!EventNode) return false;
uint32 Flags = EventNode->FunctionFlags;
Flags &= ~(FUNC_Net | FUNC_NetServer | FUNC_NetClient | FUNC_NetMulticast | FUNC_NetReliable);
const FString ModeText = Mode.ToString().ToLower();
if (ModeText == TEXT("server") || ModeText == TEXT("run_on_server"))
{
Flags |= FUNC_Net | FUNC_NetServer;
}
else if (ModeText == TEXT("client") || ModeText == TEXT("run_on_owning_client"))
{
Flags |= FUNC_Net | FUNC_NetClient;
}
else if (ModeText == TEXT("multicast") || ModeText == TEXT("netmulticast"))
{
Flags |= FUNC_Net | FUNC_NetMulticast;
}
else if (ModeText == TEXT("none") || ModeText == TEXT("local"))
{
// Keep flags cleared.
}
else
{
return false;
}
if (bReliable && (Flags & FUNC_Net))
{
Flags |= FUNC_NetReliable;
}
EventNode->Modify();
EventNode->FunctionFlags = Flags;
EventNode->ReconstructNode();
MarkBPDirty(Blueprint ? Blueprint : FBlueprintEditorUtils::FindBlueprintForGraph(Graph));
return true;
}
FString UMCPGraphLibrary::SpawnBranchNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_IfThenElse* Node = PlaceNode<UK2Node_IfThenElse>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnSequenceNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position, int32 OutputCount)
{
UK2Node_ExecutionSequence* Node = PlaceNode<UK2Node_ExecutionSequence>(Graph, Position);
if (!Node) return FString();
const int32 Extra = FMath::Max(0, OutputCount - 2);
for (int32 i = 0; i < Extra; ++i) Node->AddInputPin();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnVariableGetNode(UBlueprint* Blueprint, UEdGraph* Graph, FName VariableName, FVector2D Position)
{
if (!Blueprint || !Graph) return FString();
UK2Node_VariableGet* Node = NewObject<UK2Node_VariableGet>(Graph);
Node->CreateNewGuid();
Node->VariableReference.SetSelfMember(VariableName);
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnVariableSetNode(UBlueprint* Blueprint, UEdGraph* Graph, FName VariableName, FVector2D Position)
{
if (!Blueprint || !Graph) return FString();
UK2Node_VariableSet* Node = NewObject<UK2Node_VariableSet>(Graph);
Node->CreateNewGuid();
Node->VariableReference.SetSelfMember(VariableName);
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnSelfNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_Self* Node = PlaceNode<UK2Node_Self>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnCastNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* TargetClass, FVector2D Position)
{
if (!Blueprint || !Graph || !TargetClass) return FString();
UK2Node_DynamicCast* Node = NewObject<UK2Node_DynamicCast>(Graph);
Node->CreateNewGuid();
Node->TargetType = TargetClass;
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
// ---------------------------------------------------------------------------
// Spawners — flow / collections
// ---------------------------------------------------------------------------
FString UMCPGraphLibrary::SpawnSwitchOnIntNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_SwitchInteger* Node = PlaceNode<UK2Node_SwitchInteger>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnSwitchOnStringNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_SwitchString* Node = PlaceNode<UK2Node_SwitchString>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnSwitchOnEnumNode(UBlueprint* Blueprint, UEdGraph* Graph, UEnum* EnumType, FVector2D Position)
{
if (!Blueprint || !Graph || !EnumType) return FString();
UK2Node_SwitchEnum* Node = NewObject<UK2Node_SwitchEnum>(Graph);
Node->CreateNewGuid();
Node->SetEnum(EnumType);
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnMakeArrayNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_MakeArray* Node = PlaceNode<UK2Node_MakeArray>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnMakeStructNode(UBlueprint* Blueprint, UEdGraph* Graph, UScriptStruct* StructType, FVector2D Position)
{
if (!Blueprint || !Graph || !StructType) return FString();
UK2Node_MakeStruct* Node = NewObject<UK2Node_MakeStruct>(Graph);
Node->CreateNewGuid();
Node->StructType = StructType;
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnBreakStructNode(UBlueprint* Blueprint, UEdGraph* Graph, UScriptStruct* StructType, FVector2D Position)
{
if (!Blueprint || !Graph || !StructType) return FString();
UK2Node_BreakStruct* Node = NewObject<UK2Node_BreakStruct>(Graph);
Node->CreateNewGuid();
Node->StructType = StructType;
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnSetFieldsInStructNode(UBlueprint* Blueprint, UEdGraph* Graph, UScriptStruct* StructType, FVector2D Position)
{
if (!Blueprint || !Graph || !StructType) return FString();
UK2Node_SetFieldsInStruct* Node = NewObject<UK2Node_SetFieldsInStruct>(Graph);
Node->CreateNewGuid();
Node->StructType = StructType;
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnStandardMacroNode(UBlueprint* Blueprint, UEdGraph* Graph, FName MacroName, FVector2D Position)
{
if (!Blueprint || !Graph) return FString();
// Load StandardMacros library (engine asset).
UBlueprint* MacroLib = LoadObject<UBlueprint>(nullptr, TEXT("/Engine/EditorBlueprintResources/StandardMacros.StandardMacros"));
if (!MacroLib) return FString();
UEdGraph* MacroGraph = nullptr;
for (UEdGraph* G : MacroLib->MacroGraphs)
{
if (G && G->GetFName() == MacroName) { MacroGraph = G; break; }
}
if (!MacroGraph) return FString();
UK2Node_MacroInstance* Node = NewObject<UK2Node_MacroInstance>(Graph);
Node->CreateNewGuid();
Node->SetMacroGraph(MacroGraph);
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnSpawnActorFromClassNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_SpawnActorFromClass* Node = PlaceNode<UK2Node_SpawnActorFromClass>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnFormatTextNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_FormatText* Node = PlaceNode<UK2Node_FormatText>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnGetSubsystemNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* SubsystemClass, FVector2D Position)
{
if (!Blueprint || !Graph || !SubsystemClass) return FString();
UK2Node_GetSubsystem* Node = NewObject<UK2Node_GetSubsystem>(Graph);
Node->CreateNewGuid();
Node->Initialize(SubsystemClass);
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnLoadAssetNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_LoadAsset* Node = PlaceNode<UK2Node_LoadAsset>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
// ---------------------------------------------------------------------------
// Delegates
// ---------------------------------------------------------------------------
static bool ResolveComponentDelegate(UBlueprint* BP, FName ComponentName, FName DelegateName, FObjectProperty*& OutCompProp, FMulticastDelegateProperty*& OutDelegateProp)
{
OutCompProp = nullptr; OutDelegateProp = nullptr;
if (!BP) return false;
UClass* OwnerClass = BP->GeneratedClass ? BP->GeneratedClass : BP->ParentClass;
if (!OwnerClass) return false;
OutCompProp = FindFProperty<FObjectProperty>(OwnerClass, ComponentName);
if (!OutCompProp || !OutCompProp->PropertyClass) return false;
OutDelegateProp = FindFProperty<FMulticastDelegateProperty>(OutCompProp->PropertyClass, DelegateName);
return OutDelegateProp != nullptr;
}
FString UMCPGraphLibrary::SpawnBindDelegateNode(UBlueprint* Blueprint, UEdGraph* Graph, FName ComponentName, FName DelegateName, FVector2D Position)
{
FObjectProperty* CompProp = nullptr; FMulticastDelegateProperty* DelegateProp = nullptr;
if (!ResolveComponentDelegate(Blueprint, ComponentName, DelegateName, CompProp, DelegateProp)) return FString();
UK2Node_AddDelegate* Node = NewObject<UK2Node_AddDelegate>(Graph);
Node->CreateNewGuid();
Node->SetFromProperty(DelegateProp, /*bSelfContext*/ false, DelegateProp->GetOwnerClass());
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnUnbindDelegateNode(UBlueprint* Blueprint, UEdGraph* Graph, FName ComponentName, FName DelegateName, FVector2D Position)
{
FObjectProperty* CompProp = nullptr; FMulticastDelegateProperty* DelegateProp = nullptr;
if (!ResolveComponentDelegate(Blueprint, ComponentName, DelegateName, CompProp, DelegateProp)) return FString();
UK2Node_RemoveDelegate* Node = NewObject<UK2Node_RemoveDelegate>(Graph);
Node->CreateNewGuid();
Node->SetFromProperty(DelegateProp, false, DelegateProp->GetOwnerClass());
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnCallDelegateNode(UBlueprint* Blueprint, UEdGraph* Graph, FName DispatcherName, FVector2D Position)
{
if (!Blueprint || !Graph) return FString();
UClass* OwnerClass = Blueprint->GeneratedClass ? Blueprint->GeneratedClass : Blueprint->ParentClass;
if (!OwnerClass) return FString();
FMulticastDelegateProperty* DelegateProp = FindFProperty<FMulticastDelegateProperty>(OwnerClass, DispatcherName);
if (!DelegateProp) return FString();
UK2Node_CallDelegate* Node = NewObject<UK2Node_CallDelegate>(Graph);
Node->CreateNewGuid();
Node->SetFromProperty(DelegateProp, /*bSelfContext*/ true, OwnerClass);
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnAssignDelegateNode(UBlueprint* Blueprint, UEdGraph* Graph, FName ComponentName, FName DelegateName, FVector2D Position)
{
FObjectProperty* CompProp = nullptr; FMulticastDelegateProperty* DelegateProp = nullptr;
if (!ResolveComponentDelegate(Blueprint, ComponentName, DelegateName, CompProp, DelegateProp)) return FString();
UK2Node_AssignDelegate* Node = NewObject<UK2Node_AssignDelegate>(Graph);
Node->CreateNewGuid();
Node->SetFromProperty(DelegateProp, false, DelegateProp->GetOwnerClass());
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
bool UMCPGraphLibrary::AddEventDispatcher(UBlueprint* Blueprint, FName DispatcherName)
{
if (!Blueprint || DispatcherName.IsNone()) return false;
// Create the signature graph (void signature; user can add params via UI).
UEdGraph* SignatureGraph = FBlueprintEditorUtils::CreateNewGraph(
Blueprint, DispatcherName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
if (!SignatureGraph) return false;
SignatureGraph->bEditable = false;
SignatureGraph->bAllowDeletion = true;
Blueprint->DelegateSignatureGraphs.Add(SignatureGraph);
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
Schema->CreateDefaultNodesForGraph(*SignatureGraph);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return true;
}
// ---------------------------------------------------------------------------
// Edges & defaults
// ---------------------------------------------------------------------------
UEdGraphNode* UMCPGraphLibrary::FindNodeByGuid(UEdGraph* Graph, const FString& NodeId)
{
if (!Graph) return nullptr;
FGuid Guid;
if (!FGuid::Parse(NodeId, Guid)) return nullptr;
for (UEdGraphNode* N : Graph->Nodes) { if (N && N->NodeGuid == Guid) return N; }
return nullptr;
}
UEdGraphPin* UMCPGraphLibrary::FindPinByName(UEdGraphNode* Node, const FName& PinName)
{
if (!Node) return nullptr;
return Node->FindPin(PinName);
}
bool UMCPGraphLibrary::ConnectPins(UEdGraph* Graph, const FString& FromNodeId, FName FromPin, const FString& ToNodeId, FName ToPin)
{
if (!Graph) return false;
UEdGraphNode* A = FindNodeByGuid(Graph, FromNodeId);
UEdGraphNode* B = FindNodeByGuid(Graph, ToNodeId);
if (!A || !B) return false;
UEdGraphPin* PA = FindPinByName(A, FromPin);
UEdGraphPin* PB = FindPinByName(B, ToPin);
if (!PA || !PB) return false;
const UEdGraphSchema* Schema = Graph->GetSchema();
if (!Schema) return false;
return Schema->TryCreateConnection(PA, PB);
}
bool UMCPGraphLibrary::BreakLink(UEdGraph* Graph, const FString& FromNodeId, FName FromPin, const FString& ToNodeId, FName ToPin)
{
if (!Graph) return false;
UEdGraphNode* A = FindNodeByGuid(Graph, FromNodeId);
UEdGraphNode* B = FindNodeByGuid(Graph, ToNodeId);
if (!A || !B) return false;
UEdGraphPin* PA = FindPinByName(A, FromPin);
UEdGraphPin* PB = FindPinByName(B, ToPin);
if (!PA || !PB) return false;
PA->BreakLinkTo(PB);
return true;
}
bool UMCPGraphLibrary::BreakAllNodeLinks(UEdGraph* Graph, const FString& NodeId)
{
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) return false;
N->BreakAllNodeLinks();
return true;
}
bool UMCPGraphLibrary::SetPinDefault(UEdGraph* Graph, const FString& NodeId, FName PinName, const FString& Value)
{
if (!Graph) return false;
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) return false;
UEdGraphPin* P = FindPinByName(N, PinName);
if (!P) return false;
const UEdGraphSchema* Schema = Graph->GetSchema();
if (!Schema) return false;
Schema->TrySetDefaultValue(*P, Value);
return true;
}
// ---------------------------------------------------------------------------
// BP-level authoring
// ---------------------------------------------------------------------------
UEdGraph* UMCPGraphLibrary::AddFunctionGraph(UBlueprint* Blueprint, FName FunctionName)
{
if (!Blueprint || FunctionName.IsNone()) return nullptr;
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(
Blueprint, FunctionName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
if (!NewGraph) return nullptr;
FBlueprintEditorUtils::AddFunctionGraph(Blueprint, NewGraph, /*bIsUserCreated*/ true, (UClass*)nullptr);
return NewGraph;
}
UEdGraph* UMCPGraphLibrary::AddMacroGraph(UBlueprint* Blueprint, FName MacroName)
{
if (!Blueprint || MacroName.IsNone()) return nullptr;
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(
Blueprint, MacroName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
if (!NewGraph) return nullptr;
FBlueprintEditorUtils::AddMacroGraph(Blueprint, NewGraph, /*bIsUserCreated*/ true, (UClass*)nullptr);
return NewGraph;
}
bool UMCPGraphLibrary::AddInterface(UBlueprint* Blueprint, const FString& InterfacePath)
{
if (!Blueprint || InterfacePath.IsEmpty()) return false;
return FBlueprintEditorUtils::ImplementNewInterface(Blueprint, FTopLevelAssetPath(InterfacePath));
}
bool UMCPGraphLibrary::SetVariableDefaultValue(UBlueprint* Blueprint, FName VariableName, const FString& Value)
{
if (!Blueprint || VariableName.IsNone()) return false;
for (FBPVariableDescription& Var : Blueprint->NewVariables)
{
if (Var.VarName == VariableName)
{
Var.DefaultValue = Value;
MarkBPDirty(Blueprint);
return true;
}
}
return false;
}
bool UMCPGraphLibrary::SetVariableMetadata(UBlueprint* Blueprint, FName VariableName, bool bInstanceEditable, bool bExposeOnSpawn, const FString& Category, const FString& Tooltip)
{
if (!Blueprint || VariableName.IsNone()) return false;
// FBlueprintEditorUtils doesn't expose dedicated InstanceEditable/ExposeOnSpawn
// setters as standalone fns in 5.7. Manipulate FBPVariableDescription directly.
bool bFound = false;
for (FBPVariableDescription& Var : Blueprint->NewVariables)
{
if (Var.VarName != VariableName) continue;
bFound = true;
if (bInstanceEditable) Var.PropertyFlags &= ~CPF_DisableEditOnInstance;
else Var.PropertyFlags |= CPF_DisableEditOnInstance;
// NOTE: UE treats the mere PRESENCE of the "ExposeOnSpawn" metadata key as
// "exposed" (the value "false" does NOT un-expose it). Remove the key when
// disabling, only add it when enabling.
if (bExposeOnSpawn)
{
Var.SetMetaData(TEXT("ExposeOnSpawn"), TEXT("true"));
Var.PropertyFlags |= CPF_ExposeOnSpawn;
}
else
{
Var.RemoveMetaData(TEXT("ExposeOnSpawn"));
Var.PropertyFlags &= ~CPF_ExposeOnSpawn;
}
break;
}
if (!bFound) return false;
if (!Category.IsEmpty())
{
FBlueprintEditorUtils::SetBlueprintVariableCategory(Blueprint, VariableName, nullptr, FText::FromString(Category), false);
}
if (!Tooltip.IsEmpty())
{
FBlueprintEditorUtils::SetBlueprintVariableMetaData(Blueprint, VariableName, nullptr, TEXT("Tooltip"), Tooltip);
}
MarkBPDirty(Blueprint);
return true;
}
bool UMCPGraphLibrary::AddLocalVariable(UBlueprint* Blueprint, FName FunctionName, FName VariableName, const FString& TypeSpec, const FString& DefaultValue)
{
if (!Blueprint || FunctionName.IsNone() || VariableName.IsNone()) return false;
UEdGraph* FuncGraph = FindFunctionGraph(Blueprint, FunctionName);
if (!FuncGraph) return false;
FEdGraphPinType PinType;
const FString T = TypeSpec.ToLower();
if (T == TEXT("bool")) PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
else if (T == TEXT("int")) PinType.PinCategory = UEdGraphSchema_K2::PC_Int;
else if (T == TEXT("float")) { PinType.PinCategory = UEdGraphSchema_K2::PC_Real; PinType.PinSubCategory = UEdGraphSchema_K2::PC_Float; }
else if (T == TEXT("string")) PinType.PinCategory = UEdGraphSchema_K2::PC_String;
else if (T == TEXT("name")) PinType.PinCategory = UEdGraphSchema_K2::PC_Name;
else if (T == TEXT("text")) PinType.PinCategory = UEdGraphSchema_K2::PC_Text;
else
{
UClass* Cls = TypeSpec.Contains(TEXT("/")) ? LoadObject<UClass>(nullptr, *TypeSpec)
: FindFirstObject<UClass>(*TypeSpec, EFindFirstObjectOptions::None);
if (!Cls) return false;
PinType.PinCategory = UEdGraphSchema_K2::PC_Object;
PinType.PinSubCategoryObject = Cls;
}
return FBlueprintEditorUtils::AddLocalVariable(Blueprint, FuncGraph, VariableName, PinType, DefaultValue);
}
FString UMCPGraphLibrary::AddComponentToBlueprint(UBlueprint* Blueprint, UClass* ComponentClass, FName ComponentName)
{
if (!Blueprint || !ComponentClass) return FString();
if (!ComponentClass->IsChildOf(UActorComponent::StaticClass())) return FString();
USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript;
if (!SCS) return FString();
for (USCS_Node* Existing : SCS->GetAllNodes())
{
if (Existing && Existing->GetVariableName() == ComponentName) return Existing->GetVariableName().ToString();
}
USCS_Node* NewNode = SCS->CreateNode(ComponentClass, ComponentName);
if (!NewNode) return FString();
SCS->AddNode(NewNode);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return NewNode->GetVariableName().ToString();
}
bool UMCPGraphLibrary::AddMemberVariable(UBlueprint* Blueprint, FName VariableName, const FString& TypeSpec)
{
if (!Blueprint || VariableName.IsNone() || TypeSpec.IsEmpty()) return false;
FEdGraphPinType PinType;
const FString T = TypeSpec.ToLower();
if (T == TEXT("bool")) PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
else if (T == TEXT("int")) PinType.PinCategory = UEdGraphSchema_K2::PC_Int;
else if (T == TEXT("float")) { PinType.PinCategory = UEdGraphSchema_K2::PC_Real; PinType.PinSubCategory = UEdGraphSchema_K2::PC_Float; }
else if (T == TEXT("double")) { PinType.PinCategory = UEdGraphSchema_K2::PC_Real; PinType.PinSubCategory = UEdGraphSchema_K2::PC_Double; }
else if (T == TEXT("string")) PinType.PinCategory = UEdGraphSchema_K2::PC_String;
else if (T == TEXT("name")) PinType.PinCategory = UEdGraphSchema_K2::PC_Name;
else if (T == TEXT("text")) PinType.PinCategory = UEdGraphSchema_K2::PC_Text;
else
{
UClass* Cls = TypeSpec.Contains(TEXT("/")) ? LoadObject<UClass>(nullptr, *TypeSpec)
: FindFirstObject<UClass>(*TypeSpec, EFindFirstObjectOptions::None);
if (!Cls) return false;
PinType.PinCategory = UEdGraphSchema_K2::PC_Object;
PinType.PinSubCategoryObject = Cls;
}
return FBlueprintEditorUtils::AddMemberVariable(Blueprint, VariableName, PinType);
}
USCS_Node* UMCPGraphLibrary::FindSCSNode(UBlueprint* Blueprint, FName ComponentName)
{
if (!Blueprint || !Blueprint->SimpleConstructionScript) return nullptr;
for (USCS_Node* N : Blueprint->SimpleConstructionScript->GetAllNodes())
{
if (N && N->GetVariableName() == ComponentName) return N;
}
return nullptr;
}
bool UMCPGraphLibrary::RemoveComponent(UBlueprint* Blueprint, FName ComponentName)
{
USCS_Node* N = FindSCSNode(Blueprint, ComponentName);
if (!N) return false;
Blueprint->SimpleConstructionScript->RemoveNode(N);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return true;
}
bool UMCPGraphLibrary::AttachComponent(UBlueprint* Blueprint, FName ChildName, FName ParentName, FName SocketName)
{
USCS_Node* Child = FindSCSNode(Blueprint, ChildName);
USCS_Node* Parent = FindSCSNode(Blueprint, ParentName);
if (!Child || !Parent) return false;
USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript;
// Detach from current parent
for (USCS_Node* N : SCS->GetAllNodes())
{
if (N) N->RemoveChildNode(Child, /*bRecursive*/ false);
}
SCS->RemoveNode(Child);
// Re-add and attach
Parent->AddChildNode(Child);
if (!SocketName.IsNone()) Child->AttachToName = SocketName;
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return true;
}
bool UMCPGraphLibrary::SetRootComponent(UBlueprint* Blueprint, FName ComponentName)
{
USCS_Node* Target = FindSCSNode(Blueprint, ComponentName);
if (!Target) return false;
USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript;
// Detach from any existing parent first, then re-add as a top-level root.
// Note: SCS::RootNodes is private and has no public reorder API in 5.7, so
// the node becomes "a" root, not necessarily the FIRST root. For most BPs
// the existing root is a DefaultSceneRoot which the SCS auto-replaces when
// a new scene-root candidate appears.
for (USCS_Node* N : SCS->GetAllNodes())
{
if (N) N->RemoveChildNode(Target, /*bRecursive*/ false);
}
SCS->RemoveNode(Target);
SCS->AddNode(Target);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return true;
}
bool UMCPGraphLibrary::SetComponentProperty(UBlueprint* Blueprint, FName ComponentName, FName PropertyName, const FString& Value)
{
USCS_Node* N = FindSCSNode(Blueprint, ComponentName);
if (!N || !N->ComponentTemplate) return false;
UActorComponent* Tmpl = N->ComponentTemplate;
FProperty* Prop = FindFProperty<FProperty>(Tmpl->GetClass(), PropertyName);
if (!Prop) return false;
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Tmpl);
if (!ValuePtr) return false;
Prop->ImportText_Direct(*Value, ValuePtr, Tmpl, PPF_None);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
return true;
}
bool UMCPGraphLibrary::SetComponentTransform(UBlueprint* Blueprint, FName ComponentName, FVector Location, FRotator Rotation, FVector Scale)
{
USCS_Node* N = FindSCSNode(Blueprint, ComponentName);
if (!N) return false;
USceneComponent* Scene = Cast<USceneComponent>(N->ComponentTemplate);
if (!Scene) return false;
Scene->SetRelativeLocation(Location);
Scene->SetRelativeRotation(Rotation);
Scene->SetRelativeScale3D(Scale);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
return true;
}
bool UMCPGraphLibrary::ConfigureBoxComponent(UBlueprint* Blueprint, FName ComponentName, FVector Extent, FName CollisionProfile)
{
USCS_Node* N = FindSCSNode(Blueprint, ComponentName);
if (!N) return false;
UBoxComponent* Box = Cast<UBoxComponent>(N->ComponentTemplate);
if (!Box) return false;
Box->SetBoxExtent(Extent, false);
if (!CollisionProfile.IsNone()) Box->SetCollisionProfileName(CollisionProfile);
Box->SetGenerateOverlapEvents(true);
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
return true;
}
// ---------------------------------------------------------------------------
// Compile / introspect
// ---------------------------------------------------------------------------
bool UMCPGraphLibrary::CompileAndSaveBlueprint(UBlueprint* Blueprint)
{
if (!Blueprint) return false;
FKismetEditorUtilities::CompileBlueprint(Blueprint);
return true;
}
TArray<FString> UMCPGraphLibrary::GetCompileErrors(UBlueprint* Blueprint)
{
TArray<FString> Out;
if (!Blueprint) return Out;
// BP keeps status — return high-level state for now. Detailed messages
// require capturing FCompilerResultsLog during compile, which we do not
// wire up here; this still gives a quick health signal.
switch (Blueprint->Status)
{
case BS_UpToDate: Out.Add(TEXT("UpToDate")); break;
case BS_Dirty: Out.Add(TEXT("Dirty")); break;
case BS_Error: Out.Add(TEXT("Error")); break;
case BS_UpToDateWithWarnings: Out.Add(TEXT("UpToDateWithWarnings")); break;
case BS_Unknown: Out.Add(TEXT("Unknown")); break;
case BS_BeingCreated: Out.Add(TEXT("BeingCreated")); break;
default: Out.Add(TEXT("Other")); break;
}
return Out;
}
TArray<FString> UMCPGraphLibrary::ListFunctionsOnClass(UClass* Class)
{
TArray<FString> Out;
if (!Class) return Out;
for (TFieldIterator<UFunction> It(Class); It; ++It)
{
UFunction* F = *It;
if (!F) continue;
Out.Add(F->GetName());
}
return Out;
}
TArray<FString> UMCPGraphLibrary::ListPropertiesOnClass(UClass* Class)
{
TArray<FString> Out;
if (!Class) return Out;
for (TFieldIterator<FProperty> It(Class); It; ++It)
{
FProperty* P = *It;
if (!P) continue;
Out.Add(FString::Printf(TEXT("%s|%s"), *P->GetName(), *P->GetCPPType()));
}
return Out;
}
TArray<FString> UMCPGraphLibrary::ListDelegatesOnClass(UClass* Class)
{
TArray<FString> Out;
if (!Class) return Out;
for (TFieldIterator<FMulticastDelegateProperty> It(Class); It; ++It)
{
FMulticastDelegateProperty* D = *It;
if (!D) continue;
Out.Add(D->GetName());
}
return Out;
}
FString UMCPGraphLibrary::GetPinType(UClass* OwnerClass, FName FunctionName, FName PinName)
{
if (!OwnerClass) return FString();
UFunction* Fn = OwnerClass->FindFunctionByName(FunctionName);
if (!Fn) return FString();
FProperty* Param = Fn->FindPropertyByName(PinName);
if (!Param) return FString();
return Param->GetCPPType();
}
// ===========================================================================
// Tier 2 spawners
// ===========================================================================
FString UMCPGraphLibrary::SpawnKnotNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_Knot* Node = PlaceNode<UK2Node_Knot>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnCommentNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position, FVector2D Size, const FString& Comment)
{
if (!Graph) return FString();
UEdGraphNode_Comment* Node = NewObject<UEdGraphNode_Comment>(Graph);
Node->CreateNewGuid();
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Node->NodeWidth = FMath::Max(64, static_cast<int32>(Size.X));
Node->NodeHeight = FMath::Max(64, static_cast<int32>(Size.Y));
Node->NodeComment = Comment;
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnEnumLiteralNode(UBlueprint* Blueprint, UEdGraph* Graph, UEnum* EnumType, FVector2D Position)
{
if (!Graph || !EnumType) return FString();
UK2Node_EnumLiteral* Node = NewObject<UK2Node_EnumLiteral>(Graph);
Node->CreateNewGuid();
Node->Enum = EnumType;
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnGetClassDefaultsNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* TargetClass, FVector2D Position)
{
if (!Graph || !TargetClass) return FString();
UK2Node_GetClassDefaults* Node = NewObject<UK2Node_GetClassDefaults>(Graph);
Node->CreateNewGuid();
Node->NodePosX = static_cast<int32>(Position.X);
Node->NodePosY = static_cast<int32>(Position.Y);
Graph->AddNode(Node, true, false);
Node->PostPlacedNewNode();
Node->AllocateDefaultPins();
// Set the class on the input pin
if (UEdGraphPin* ClassPin = Node->FindPin(TEXT("Class")))
{
Graph->GetSchema()->TrySetDefaultObject(*ClassPin, TargetClass);
Node->ReconstructNode();
}
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnFunctionResultNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_FunctionResult* Node = PlaceNode<UK2Node_FunctionResult>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnGetArrayItemNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position)
{
UK2Node_GetArrayItem* Node = PlaceNode<UK2Node_GetArrayItem>(Graph, Position);
if (!Node) return FString();
MarkBPDirty(Blueprint);
return Node->NodeGuid.ToString();
}
FString UMCPGraphLibrary::SpawnPromotableOperatorNode(UBlueprint* Blueprint, UEdGraph* Graph, FName OperatorName, FVector2D Position)
{
// Promotable ops in UE are really KismetMathLibrary call_function nodes (Add_IntInt,
// Subtract_FloatFloat, etc.) that the editor auto-promotes. Easiest portable impl:
// route to KismetMathLibrary by symbolic operator -> wildcard function name.
if (!Blueprint || !Graph) return FString();
static const TMap<FName, FName> OpMap = {
{ TEXT("+"), TEXT("Add_IntInt") },
{ TEXT("-"), TEXT("Subtract_IntInt") },
{ TEXT("*"), TEXT("Multiply_IntInt") },
{ TEXT("/"), TEXT("Divide_IntInt") },
{ TEXT("=="), TEXT("EqualEqual_IntInt") },
{ TEXT("!="), TEXT("NotEqual_IntInt") },
{ TEXT("<"), TEXT("Less_IntInt") },
{ TEXT(">"), TEXT("Greater_IntInt") },
{ TEXT("<="), TEXT("LessEqual_IntInt") },
{ TEXT(">="), TEXT("GreaterEqual_IntInt") },
};
const FName* Mapped = OpMap.Find(OperatorName);
if (!Mapped) return FString();
UClass* KML = LoadClass<UObject>(nullptr, TEXT("/Script/Engine.KismetMathLibrary"));
if (!KML) return FString();
return SpawnCallFunctionNode(Blueprint, Graph, KML, *Mapped, Position);
}
bool UMCPGraphLibrary::AddCasePinToSwitch(UEdGraph* Graph, const FString& NodeId)
{
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) return false;
UK2Node_Switch* Sw = Cast<UK2Node_Switch>(N);
if (!Sw) return false;
Sw->AddPinToSwitchNode();
Sw->ReconstructNode();
if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP);
return true;
}
bool UMCPGraphLibrary::FormatTextSetFormat(UEdGraph* Graph, const FString& NodeId, const FString& Format)
{
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) return false;
UK2Node_FormatText* FT = Cast<UK2Node_FormatText>(N);
if (!FT) return false;
UEdGraphPin* FormatPin = FT->FindPin(TEXT("Format"));
if (!FormatPin) return false;
Graph->GetSchema()->TrySetDefaultText(*FormatPin, FText::FromString(Format));
FT->PinDefaultValueChanged(FormatPin);
FT->ReconstructNode();
if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP);
return true;
}
bool UMCPGraphLibrary::ReconstructNode(UEdGraph* Graph, const FString& NodeId)
{
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) return false;
N->ReconstructNode();
if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP);
return true;
}
// ===========================================================================
// Authoring — rename / delete / function params
// ===========================================================================
bool UMCPGraphLibrary::RenameVariable(UBlueprint* Blueprint, FName OldName, FName NewName)
{
if (!Blueprint || OldName.IsNone() || NewName.IsNone()) return false;
FBlueprintEditorUtils::RenameMemberVariable(Blueprint, OldName, NewName);
return true;
}
bool UMCPGraphLibrary::DeleteVariable(UBlueprint* Blueprint, FName VariableName)
{
if (!Blueprint || VariableName.IsNone()) return false;
FBlueprintEditorUtils::RemoveMemberVariable(Blueprint, VariableName);
return true;
}
bool UMCPGraphLibrary::RenameFunction(UBlueprint* Blueprint, FName OldName, FName NewName)
{
if (!Blueprint || OldName.IsNone() || NewName.IsNone()) return false;
UEdGraph* G = FindFunctionGraph(Blueprint, OldName);
if (!G) return false;
FBlueprintEditorUtils::RenameGraph(G, NewName.ToString());
return true;
}
bool UMCPGraphLibrary::RemoveFunctionGraph(UBlueprint* Blueprint, FName FunctionName)
{
if (!Blueprint || FunctionName.IsNone()) return false;
UEdGraph* G = nullptr;
for (UEdGraph* Fg : Blueprint->FunctionGraphs) { if (Fg && Fg->GetFName() == FunctionName) { G = Fg; break; } }
if (!G) return false;
FBlueprintEditorUtils::RemoveGraph(Blueprint, G, EGraphRemoveFlags::Recompile);
return true;
}
bool UMCPGraphLibrary::RemoveMacroGraph(UBlueprint* Blueprint, FName MacroName)
{
if (!Blueprint || MacroName.IsNone()) return false;
UEdGraph* G = nullptr;
for (UEdGraph* Mg : Blueprint->MacroGraphs) { if (Mg && Mg->GetFName() == MacroName) { G = Mg; break; } }
if (!G) return false;
FBlueprintEditorUtils::RemoveGraph(Blueprint, G, EGraphRemoveFlags::Recompile);
return true;
}
static bool ParsePinTypeFromSpec(const FString& TypeSpec, FEdGraphPinType& OutPinType)
{
const FString T = TypeSpec.ToLower();
if (T == TEXT("bool")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_Boolean;
else if (T == TEXT("int")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_Int;
else if (T == TEXT("float")) { OutPinType.PinCategory = UEdGraphSchema_K2::PC_Real; OutPinType.PinSubCategory = UEdGraphSchema_K2::PC_Float; }
else if (T == TEXT("double")) { OutPinType.PinCategory = UEdGraphSchema_K2::PC_Real; OutPinType.PinSubCategory = UEdGraphSchema_K2::PC_Double; }
else if (T == TEXT("string")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_String;
else if (T == TEXT("name")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_Name;
else if (T == TEXT("text")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_Text;
else
{
UClass* Cls = TypeSpec.Contains(TEXT("/")) ? LoadObject<UClass>(nullptr, *TypeSpec)
: FindFirstObject<UClass>(*TypeSpec, EFindFirstObjectOptions::None);
if (!Cls) return false;
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Object;
OutPinType.PinSubCategoryObject = Cls;
}
return true;
}
bool UMCPGraphLibrary::AddFunctionParameter(UBlueprint* Blueprint, FName FunctionName, FName ParamName, const FString& TypeSpec, const FString& Direction)
{
if (!Blueprint || FunctionName.IsNone() || ParamName.IsNone()) return false;
UEdGraph* G = FindFunctionGraph(Blueprint, FunctionName);
if (!G) return false;
FEdGraphPinType PinType;
if (!ParsePinTypeFromSpec(TypeSpec, PinType)) return false;
const bool bInput = Direction.IsEmpty() || Direction.Equals(TEXT("input"), ESearchCase::IgnoreCase);
// Inputs go on the EntryNode; outputs on the ResultNode (created if missing).
UK2Node_EditablePinBase* TargetNode = nullptr;
for (UEdGraphNode* N : G->Nodes)
{
if (bInput && N->IsA<UK2Node_FunctionEntry>()) { TargetNode = Cast<UK2Node_EditablePinBase>(N); break; }
if (!bInput && N->IsA<UK2Node_FunctionResult>()) { TargetNode = Cast<UK2Node_EditablePinBase>(N); break; }
}
if (!bInput && !TargetNode)
{
// Auto-spawn result node if missing
FString NewGuid = SpawnFunctionResultNode(Blueprint, G, FVector2D(600, 0));
if (UEdGraphNode* R = FindNodeByGuid(G, NewGuid)) TargetNode = Cast<UK2Node_EditablePinBase>(R);
}
if (!TargetNode) return false;
TSharedPtr<FUserPinInfo> NewPinInfo = MakeShareable(new FUserPinInfo());
NewPinInfo->PinName = ParamName;
NewPinInfo->PinType = PinType;
NewPinInfo->DesiredPinDirection = bInput ? EGPD_Output : EGPD_Input;
TargetNode->UserDefinedPins.Add(NewPinInfo);
TargetNode->ReconstructNode();
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return true;
}
bool UMCPGraphLibrary::RemoveFunctionParameter(UBlueprint* Blueprint, FName FunctionName, FName ParamName)
{
if (!Blueprint || FunctionName.IsNone() || ParamName.IsNone()) return false;
UEdGraph* G = FindFunctionGraph(Blueprint, FunctionName);
if (!G) return false;
bool bChanged = false;
for (UEdGraphNode* N : G->Nodes)
{
UK2Node_EditablePinBase* E = Cast<UK2Node_EditablePinBase>(N);
if (!E) continue;
for (int32 i = E->UserDefinedPins.Num() - 1; i >= 0; --i)
{
if (E->UserDefinedPins[i]->PinName == ParamName)
{
E->RemoveUserDefinedPin(E->UserDefinedPins[i]);
bChanged = true;
}
}
if (bChanged) E->ReconstructNode();
}
if (bChanged) FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return bChanged;
}
// ===========================================================================
// UMG Designer / WidgetTree
// ===========================================================================
FString UMCPGraphLibrary::DescribeWidgetTreeJson(UWidgetBlueprint* Blueprint)
{
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
UWidgetTree* Tree = MCPGetWidgetTree(Blueprint);
if (!Blueprint || !Tree)
{
Root->SetBoolField(TEXT("ok"), false);
Root->SetStringField(TEXT("error"), TEXT("not a WidgetBlueprint or missing WidgetTree"));
return MCPWidgetJsonStringify(Root);
}
Root->SetBoolField(TEXT("ok"), true);
Root->SetStringField(TEXT("path"), Blueprint->GetPathName());
if (Tree->RootWidget)
{
Root->SetStringField(TEXT("root"), Tree->RootWidget->GetFName().ToString());
}
TArray<UWidget*> Widgets;
Tree->GetAllWidgets(Widgets);
TArray<TSharedPtr<FJsonValue>> Items;
for (UWidget* Widget : Widgets)
{
if (Widget)
{
Items.Add(MakeShared<FJsonValueObject>(WidgetToJson(Widget)));
}
}
Root->SetArrayField(TEXT("widgets"), Items);
Root->SetNumberField(TEXT("count"), Items.Num());
return MCPWidgetJsonStringify(Root);
}
FString UMCPGraphLibrary::GetWidgetPropertiesJson(UWidgetBlueprint* Blueprint, FName WidgetName)
{
return MCPWidgetJsonStringify(ExportObjectPropertiesJson(MCPFindWidget(Blueprint, WidgetName)));
}
FString UMCPGraphLibrary::GetWidgetSlotPropertiesJson(UWidgetBlueprint* Blueprint, FName WidgetName)
{
UWidget* Widget = MCPFindWidget(Blueprint, WidgetName);
return MCPWidgetJsonStringify(ExportObjectPropertiesJson(Widget ? Widget->Slot : nullptr));
}
FString UMCPGraphLibrary::AddWidgetToTree(UWidgetBlueprint* Blueprint, UClass* WidgetClass, FName WidgetName, FName ParentName, int32 Index, bool bIsVariable)
{
UWidgetTree* Tree = MCPGetWidgetTree(Blueprint);
if (!Blueprint || !Tree || !WidgetClass || !WidgetClass->IsChildOf(UWidget::StaticClass()))
{
return FString();
}
FName FinalName = WidgetName;
if (FinalName.IsNone())
{
FinalName = MakeUniqueObjectName(Tree, WidgetClass, WidgetClass->GetFName());
}
else if (Tree->FindWidget(FinalName))
{
return FString();
}
UWidget* Parent = ParentName.IsNone() ? nullptr : MCPFindWidget(Blueprint, ParentName);
if (!ParentName.IsNone() && !Parent)
{
return FString();
}
Tree->SetFlags(RF_Transactional);
Tree->Modify();
UWidget* Widget = Tree->ConstructWidget<UWidget>(WidgetClass, FinalName);
if (!Widget)
{
return FString();
}
Widget->SetFlags(RF_Transactional);
Widget->CreatedFromPalette();
Widget->bIsVariable = bIsVariable;
if (!AddExistingWidgetToParent(Blueprint, Widget, Parent, Index))
{
Tree->RemoveWidget(Widget);
return FString();
}
if (Widget->bIsVariable)
{
Blueprint->OnVariableAdded(Widget->GetFName());
}
MarkWidgetBPDirty(Blueprint);
return Widget->GetFName().ToString();
}
bool UMCPGraphLibrary::RemoveWidgetFromTree(UWidgetBlueprint* Blueprint, FName WidgetName)
{
UWidget* Widget = MCPFindWidget(Blueprint, WidgetName);
if (!Blueprint || !Widget)
{
return false;
}
const bool bWasVariable = Widget->bIsVariable;
const FName RemovedName = Widget->GetFName();
TSet<UWidget*> Widgets;
Widgets.Add(Widget);
FWidgetBlueprintEditorUtils::DeleteWidgets(Blueprint, Widgets, FWidgetBlueprintEditorUtils::EDeleteWidgetWarningType::DeleteSilently);
if (bWasVariable)
{
Blueprint->OnVariableRemoved(RemovedName);
}
MarkWidgetBPDirty(Blueprint);
return true;
}
bool UMCPGraphLibrary::RenameWidgetInTree(UWidgetBlueprint* Blueprint, FName OldName, FName NewName)
{
UWidgetTree* Tree = MCPGetWidgetTree(Blueprint);
UWidget* Widget = MCPFindWidget(Blueprint, OldName);
if (!Blueprint || !Tree || !Widget || NewName.IsNone() || Tree->FindWidget(NewName))
{
return false;
}
const bool bWasVariable = Widget->bIsVariable;
Widget->SetFlags(RF_Transactional);
Widget->Modify();
const FName PreviousName = Widget->GetFName();
if (!Widget->Rename(*NewName.ToString(), Tree, REN_DontCreateRedirectors))
{
return false;
}
Widget->SetDisplayLabel(TEXT(""));
if (bWasVariable)
{
Blueprint->OnVariableRenamed(PreviousName, NewName);
}
MarkWidgetBPDirty(Blueprint);
return true;
}
bool UMCPGraphLibrary::MoveWidgetInTree(UWidgetBlueprint* Blueprint, FName WidgetName, FName NewParentName, int32 Index)
{
UWidgetTree* Tree = MCPGetWidgetTree(Blueprint);
UWidget* Widget = MCPFindWidget(Blueprint, WidgetName);
UWidget* NewParent = NewParentName.IsNone() ? nullptr : MCPFindWidget(Blueprint, NewParentName);
if (!Blueprint || !Tree || !Widget || (!NewParentName.IsNone() && !NewParent) || Widget == NewParent)
{
return false;
}
if (NewParent && NewParent->IsChildOf(Widget))
{
return false;
}
Tree->Modify();
if (Widget == Tree->RootWidget)
{
Tree->RootWidget = nullptr;
}
else if (UPanelWidget* OldParent = Cast<UPanelWidget>(Widget->Slot ? Widget->Slot->Parent : nullptr))
{
OldParent->Modify();
OldParent->RemoveChild(Widget);
}
else
{
Tree->RemoveWidget(Widget);
UWidgetTree::TryMoveWidgetToNewTree(Widget, Tree);
}
if (!AddExistingWidgetToParent(Blueprint, Widget, NewParent, Index))
{
return false;
}
MarkWidgetBPDirty(Blueprint);
return true;
}
bool UMCPGraphLibrary::SetRootWidget(UWidgetBlueprint* Blueprint, FName WidgetName)
{
UWidgetTree* Tree = MCPGetWidgetTree(Blueprint);
UWidget* Widget = MCPFindWidget(Blueprint, WidgetName);
if (!Blueprint || !Tree || !Widget)
{
return false;
}
Tree->Modify();
if (UPanelWidget* OldParent = Cast<UPanelWidget>(Widget->Slot ? Widget->Slot->Parent : nullptr))
{
OldParent->Modify();
OldParent->RemoveChild(Widget);
}
Tree->RootWidget = Widget;
MarkWidgetBPDirty(Blueprint);
return true;
}
bool UMCPGraphLibrary::SetWidgetIsVariable(UWidgetBlueprint* Blueprint, FName WidgetName, bool bIsVariable)
{
UWidget* Widget = MCPFindWidget(Blueprint, WidgetName);
if (!Blueprint || !Widget || Widget->bIsVariable == bIsVariable)
{
return Widget != nullptr;
}
Widget->Modify();
Widget->bIsVariable = bIsVariable;
if (bIsVariable)
{
Blueprint->OnVariableAdded(Widget->GetFName());
}
else
{
Blueprint->OnVariableRemoved(Widget->GetFName());
}
MarkWidgetBPDirty(Blueprint);
return true;
}
bool UMCPGraphLibrary::SetWidgetProperty(UWidgetBlueprint* Blueprint, FName WidgetName, FName PropertyName, const FString& Value)
{
UWidget* Widget = MCPFindWidget(Blueprint, WidgetName);
const bool bOk = SetObjectPropertyFromString(Widget, PropertyName, Value);
if (bOk)
{
MarkWidgetBPDirty(Blueprint, false);
}
return bOk;
}
bool UMCPGraphLibrary::SetWidgetSlotProperty(UWidgetBlueprint* Blueprint, FName WidgetName, FName PropertyName, const FString& Value)
{
UWidget* Widget = MCPFindWidget(Blueprint, WidgetName);
const bool bOk = SetObjectPropertyFromString(Widget ? Widget->Slot : nullptr, PropertyName, Value);
if (bOk)
{
MarkWidgetBPDirty(Blueprint, false);
}
return bOk;
}
bool UMCPGraphLibrary::SetCanvasSlotLayout(UWidgetBlueprint* Blueprint, FName WidgetName, FVector2D Position, FVector2D Size, FVector2D AnchorsMinimum, FVector2D AnchorsMaximum, FVector2D Alignment, bool bAutoSize, int32 ZOrder)
{
UWidget* Widget = MCPFindWidget(Blueprint, WidgetName);
UCanvasPanelSlot* Slot = Widget ? Cast<UCanvasPanelSlot>(Widget->Slot) : nullptr;
if (!Slot)
{
return false;
}
Slot->SetFlags(RF_Transactional);
Slot->Modify();
Slot->SetPosition(Position);
Slot->SetSize(Size);
Slot->SetAnchors(FAnchors(AnchorsMinimum.X, AnchorsMinimum.Y, AnchorsMaximum.X, AnchorsMaximum.Y));
Slot->SetAlignment(Alignment);
Slot->SetAutoSize(bAutoSize);
Slot->SetZOrder(ZOrder);
Slot->SynchronizeProperties();
MarkWidgetBPDirty(Blueprint, false);
return true;
}
bool UMCPGraphLibrary::SetNamedSlotContent(UWidgetBlueprint* Blueprint, FName HostWidgetName, FName SlotName, FName ContentWidgetName)
{
UWidget* Host = MCPFindWidget(Blueprint, HostWidgetName);
UWidget* Content = MCPFindWidget(Blueprint, ContentWidgetName);
INamedSlotInterface* NamedSlots = Cast<INamedSlotInterface>(Host);
if (!Blueprint || !NamedSlots || !Content || SlotName.IsNone())
{
return false;
}
Host->Modify();
Content->Modify();
NamedSlots->SetContentForSlot(SlotName, Content);
MarkWidgetBPDirty(Blueprint);
return true;
}
bool UMCPGraphLibrary::ClearNamedSlotContent(UWidgetBlueprint* Blueprint, FName HostWidgetName, FName SlotName)
{
UWidget* Host = MCPFindWidget(Blueprint, HostWidgetName);
INamedSlotInterface* NamedSlots = Cast<INamedSlotInterface>(Host);
if (!Blueprint || !NamedSlots || SlotName.IsNone())
{
return false;
}
Host->Modify();
NamedSlots->SetContentForSlot(SlotName, nullptr);
MarkWidgetBPDirty(Blueprint);
return true;
}
bool UMCPGraphLibrary::SetDesiredFocusWidget(UWidgetBlueprint* Blueprint, FName WidgetName)
{
if (!Blueprint || WidgetName.IsNone() || !MCPFindWidget(Blueprint, WidgetName))
{
return false;
}
FWidgetBlueprintEditorUtils::SetDesiredFocus(Blueprint, WidgetName);
MarkWidgetBPDirty(Blueprint, false);
return true;
}
bool UMCPGraphLibrary::CreateWidgetFloatAnimation(UWidgetBlueprint* Blueprint, FName AnimName, FName WidgetName, FName PropertyName, const TArray<float>& Times, const TArray<float>& Values)
{
if (!Blueprint || AnimName.IsNone() || WidgetName.IsNone() || PropertyName.IsNone())
return false;
if (Times.Num() == 0 || Times.Num() != Values.Num())
return false;
UWidget* Target = MCPFindWidget(Blueprint, WidgetName);
if (!Target)
return false;
// No duplicates.
for (UWidgetAnimation* Existing : Blueprint->Animations)
if (Existing && Existing->GetFName() == AnimName)
return false;
UWidgetAnimation* Anim = NewObject<UWidgetAnimation>(Blueprint, AnimName, RF_Transactional);
Anim->MovieScene = NewObject<UMovieScene>(Anim, AnimName, RF_Transactional);
// Millisecond tick resolution keeps second->frame conversion exact for our keys.
Anim->MovieScene->SetTickResolutionDirectly(FFrameRate(1000, 1));
Anim->MovieScene->SetDisplayRate(FFrameRate(60, 1));
// Possess the target widget and register the runtime binding.
const FGuid BindingGuid = Anim->MovieScene->AddPossessable(WidgetName.ToString(), Target->GetClass());
FWidgetAnimationBinding NewBinding;
NewBinding.WidgetName = WidgetName;
NewBinding.AnimationGuid = BindingGuid;
NewBinding.bIsRootWidget = false;
Anim->AnimationBindings.Add(NewBinding);
// Float track on the requested property (e.g. RenderOpacity).
UMovieSceneFloatTrack* Track = Anim->MovieScene->AddTrack<UMovieSceneFloatTrack>(BindingGuid);
if (!Track)
return false;
Track->SetPropertyNameAndPath(PropertyName, PropertyName.ToString());
UMovieSceneFloatSection* Section = Cast<UMovieSceneFloatSection>(Track->CreateNewSection());
if (!Section)
return false;
Track->AddSection(*Section);
TArrayView<FMovieSceneFloatChannel*> Channels = Section->GetChannelProxy().GetChannels<FMovieSceneFloatChannel>();
if (Channels.Num() == 0)
return false;
FMovieSceneFloatChannel* Channel = Channels[0];
int32 LastFrame = 0;
for (int32 i = 0; i < Times.Num(); ++i)
{
const int32 FrameMs = FMath::RoundToInt(Times[i] * 1000.0f);
Channel->AddLinearKey(FFrameNumber(FrameMs), Values[i]);
LastFrame = FMath::Max(LastFrame, FrameMs);
}
const TRange<FFrameNumber> PlaybackRange(FFrameNumber(0), FFrameNumber(LastFrame));
Section->SetRange(PlaybackRange);
Anim->MovieScene->SetPlaybackRange(PlaybackRange, /*bAlwaysMarkDirty*/ false);
Blueprint->Animations.Add(Anim);
MarkWidgetBPDirty(Blueprint, /*bStructural*/ true);
return true;
}
// ===========================================================================
// Compile diagnostics + graph dump
// ===========================================================================
TArray<FString> UMCPGraphLibrary::CompileWithMessages(UBlueprint* Blueprint)
{
TArray<FString> Out;
if (!Blueprint) return Out;
FCompilerResultsLog Results;
Results.SetSourcePath(Blueprint->GetPathName());
Results.BeginEvent(TEXT("MCPCompile"));
FKismetEditorUtilities::CompileBlueprint(Blueprint, EBlueprintCompileOptions::None, &Results);
Results.EndEvent();
auto AppendList = [&Out](const TArray<TSharedRef<FTokenizedMessage>>& Msgs, const TCHAR* Sev)
{
for (const TSharedRef<FTokenizedMessage>& M : Msgs)
{
FString NodeName;
for (const TSharedRef<IMessageToken>& Tk : M->GetMessageTokens())
{
if (Tk->GetType() == EMessageToken::Object)
{
TSharedRef<FUObjectToken> Obj = StaticCastSharedRef<FUObjectToken>(Tk);
if (UObject* Resolved = Obj->GetObject().Get())
{
NodeName = Resolved->GetName();
break;
}
}
}
Out.Add(FString::Printf(TEXT("%s|%s|%s"), Sev, *NodeName, *M->ToText().ToString()));
}
};
AppendList(Results.Messages, TEXT("INFO"));
return Out;
}
static TSharedRef<FJsonObject> NodeToJson(UEdGraphNode* N)
{
TSharedRef<FJsonObject> J = MakeShared<FJsonObject>();
J->SetStringField(TEXT("guid"), N->NodeGuid.ToString());
J->SetStringField(TEXT("class"), N->GetClass()->GetName());
J->SetNumberField(TEXT("x"), N->NodePosX);
J->SetNumberField(TEXT("y"), N->NodePosY);
J->SetStringField(TEXT("title"), N->GetNodeTitle(ENodeTitleType::ListView).ToString());
if (!N->NodeComment.IsEmpty()) J->SetStringField(TEXT("comment"), N->NodeComment);
if (UEdGraphNode_Comment* CN = Cast<UEdGraphNode_Comment>(N))
{
J->SetNumberField(TEXT("w"), CN->NodeWidth);
J->SetNumberField(TEXT("h"), CN->NodeHeight);
if (!CN->NodeComment.IsEmpty()) J->SetStringField(TEXT("comment"), CN->NodeComment);
}
// Type-specific target hint
FString Target;
if (UK2Node_CallFunction* CF = Cast<UK2Node_CallFunction>(N))
{
const FMemberReference& Ref = CF->FunctionReference;
UClass* OwnerCls = Ref.GetMemberParentClass();
Target = FString::Printf(TEXT("%s.%s"), OwnerCls ? *OwnerCls->GetName() : TEXT("?"), *Ref.GetMemberName().ToString());
}
else if (UK2Node_Event* EV = Cast<UK2Node_Event>(N))
{
Target = EV->EventReference.GetMemberName().ToString();
}
else if (UK2Node_VariableGet* VG = Cast<UK2Node_VariableGet>(N))
{
Target = VG->VariableReference.GetMemberName().ToString();
}
else if (UK2Node_VariableSet* VS = Cast<UK2Node_VariableSet>(N))
{
Target = VS->VariableReference.GetMemberName().ToString();
}
if (!Target.IsEmpty()) J->SetStringField(TEXT("target"), Target);
TArray<TSharedPtr<FJsonValue>> Pins;
for (UEdGraphPin* P : N->Pins)
{
if (!P) continue;
TSharedRef<FJsonObject> PJ = MakeShared<FJsonObject>();
PJ->SetStringField(TEXT("name"), P->PinName.ToString());
PJ->SetStringField(TEXT("dir"), P->Direction == EGPD_Input ? TEXT("in") : TEXT("out"));
PJ->SetStringField(TEXT("type"), P->PinType.PinCategory.ToString());
if (!P->PinType.PinSubCategory.IsNone())
PJ->SetStringField(TEXT("subtype"), P->PinType.PinSubCategory.ToString());
if (P->PinType.PinSubCategoryObject.IsValid())
PJ->SetStringField(TEXT("subobj"), P->PinType.PinSubCategoryObject->GetPathName());
if (P->PinType.IsArray()) PJ->SetBoolField(TEXT("array"), true);
if (P->PinType.IsSet()) PJ->SetBoolField(TEXT("set"), true);
if (P->PinType.IsMap()) PJ->SetBoolField(TEXT("map"), true);
if (P->PinType.bIsReference) PJ->SetBoolField(TEXT("byref"), true);
if (!P->DefaultValue.IsEmpty()) PJ->SetStringField(TEXT("default"), P->DefaultValue);
if (P->DefaultObject) PJ->SetStringField(TEXT("default_obj"), P->DefaultObject->GetPathName());
TArray<TSharedPtr<FJsonValue>> Links;
for (UEdGraphPin* L : P->LinkedTo)
{
if (!L || !L->GetOwningNode()) continue;
TSharedRef<FJsonObject> LJ = MakeShared<FJsonObject>();
LJ->SetStringField(TEXT("node"), L->GetOwningNode()->NodeGuid.ToString());
LJ->SetStringField(TEXT("pin"), L->PinName.ToString());
Links.Add(MakeShared<FJsonValueObject>(LJ));
}
if (Links.Num()) PJ->SetArrayField(TEXT("links"), Links);
Pins.Add(MakeShared<FJsonValueObject>(PJ));
}
J->SetArrayField(TEXT("pins"), Pins);
return J;
}
FString UMCPGraphLibrary::DumpGraphToJson(UEdGraph* Graph)
{
if (!Graph) return TEXT("{\"error\":\"null graph\"}");
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
Root->SetStringField(TEXT("graph_name"), Graph->GetName());
TArray<TSharedPtr<FJsonValue>> Nodes;
for (UEdGraphNode* N : Graph->Nodes)
{
if (!N) continue;
Nodes.Add(MakeShared<FJsonValueObject>(NodeToJson(N)));
}
Root->SetArrayField(TEXT("nodes"), Nodes);
FString Out;
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Out);
FJsonSerializer::Serialize(Root, Writer);
return Out;
}
// ===========================================================================
// Discovery v0.3
// ===========================================================================
static FString JsonStringify(TSharedRef<FJsonObject> Obj)
{
FString Out;
TSharedRef<TJsonWriter<>> W = TJsonWriterFactory<>::Create(&Out);
FJsonSerializer::Serialize(Obj, W);
return Out;
}
FString UMCPGraphLibrary::ListGraphsJson(UBlueprint* Blueprint)
{
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
if (!Blueprint) { Root->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(Root); }
auto AsList = [](const TArray<UEdGraph*>& Gs)
{
TArray<TSharedPtr<FJsonValue>> A;
for (UEdGraph* G : Gs) if (G) A.Add(MakeShared<FJsonValueString>(G->GetName()));
return A;
};
Root->SetArrayField(TEXT("event"), AsList(Blueprint->UbergraphPages));
Root->SetArrayField(TEXT("functions"), AsList(Blueprint->FunctionGraphs));
Root->SetArrayField(TEXT("macros"), AsList(Blueprint->MacroGraphs));
Root->SetArrayField(TEXT("delegates"), AsList(Blueprint->DelegateSignatureGraphs));
return JsonStringify(Root);
}
FString UMCPGraphLibrary::DescribeBlueprintJson(UBlueprint* Blueprint)
{
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
if (!Blueprint) { Root->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(Root); }
Root->SetStringField(TEXT("path"), Blueprint->GetPathName());
Root->SetStringField(TEXT("parent"), Blueprint->ParentClass ? Blueprint->ParentClass->GetPathName() : TEXT("?"));
// Interfaces
TArray<TSharedPtr<FJsonValue>> Interfaces;
for (const FBPInterfaceDescription& I : Blueprint->ImplementedInterfaces)
{
if (I.Interface) Interfaces.Add(MakeShared<FJsonValueString>(I.Interface->GetPathName()));
}
Root->SetArrayField(TEXT("interfaces"), Interfaces);
// Variables
TArray<TSharedPtr<FJsonValue>> Vars;
for (const FBPVariableDescription& V : Blueprint->NewVariables)
{
TSharedRef<FJsonObject> VJ = MakeShared<FJsonObject>();
VJ->SetStringField(TEXT("name"), V.VarName.ToString());
VJ->SetStringField(TEXT("type"), V.VarType.PinCategory.ToString());
if (V.VarType.PinSubCategoryObject.IsValid())
VJ->SetStringField(TEXT("subobj"), V.VarType.PinSubCategoryObject->GetPathName());
VJ->SetBoolField(TEXT("instance_editable"), !(V.PropertyFlags & CPF_DisableEditOnInstance));
VJ->SetBoolField(TEXT("expose_on_spawn"), !!(V.PropertyFlags & CPF_ExposeOnSpawn));
if (!V.DefaultValue.IsEmpty()) VJ->SetStringField(TEXT("default"), V.DefaultValue);
if (!V.Category.IsEmpty()) VJ->SetStringField(TEXT("category"), V.Category.ToString());
Vars.Add(MakeShared<FJsonValueObject>(VJ));
}
Root->SetArrayField(TEXT("variables"), Vars);
// Components (SCS)
TArray<TSharedPtr<FJsonValue>> Comps;
if (USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript)
{
for (USCS_Node* SN : SCS->GetAllNodes())
{
if (!SN) continue;
TSharedRef<FJsonObject> CJ = MakeShared<FJsonObject>();
CJ->SetStringField(TEXT("name"), SN->GetVariableName().ToString());
if (SN->ComponentTemplate)
CJ->SetStringField(TEXT("class"), SN->ComponentTemplate->GetClass()->GetPathName());
if (!SN->AttachToName.IsNone()) CJ->SetStringField(TEXT("socket"), SN->AttachToName.ToString());
USCS_Node* P = nullptr;
for (USCS_Node* M : SCS->GetAllNodes())
if (M && M->GetChildNodes().Contains(SN)) { P = M; break; }
if (P) CJ->SetStringField(TEXT("parent"), P->GetVariableName().ToString());
Comps.Add(MakeShared<FJsonValueObject>(CJ));
}
}
Root->SetArrayField(TEXT("components"), Comps);
// Functions
TArray<TSharedPtr<FJsonValue>> Fns;
for (UEdGraph* G : Blueprint->FunctionGraphs)
if (G) Fns.Add(MakeShared<FJsonValueString>(G->GetName()));
Root->SetArrayField(TEXT("functions"), Fns);
// Dispatchers (multicast delegate props on generated class)
TArray<TSharedPtr<FJsonValue>> Disps;
if (UClass* GC = Blueprint->GeneratedClass)
{
for (TFieldIterator<FMulticastDelegateProperty> It(GC, EFieldIteratorFlags::ExcludeSuper); It; ++It)
{
FMulticastDelegateProperty* D = *It;
if (D) Disps.Add(MakeShared<FJsonValueString>(D->GetName()));
}
}
Root->SetArrayField(TEXT("dispatchers"), Disps);
return JsonStringify(Root);
}
FString UMCPGraphLibrary::GetSelectedNodesJson(UBlueprint* Blueprint)
{
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
if (!Blueprint || !GEditor) { Root->SetStringField(TEXT("error"), TEXT("no editor or bp")); return JsonStringify(Root); }
UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
IAssetEditorInstance* Inst = AES ? AES->FindEditorForAsset(Blueprint, /*bFocusIfOpen*/ false) : nullptr;
if (!Inst) { Root->SetStringField(TEXT("error"), TEXT("blueprint not open in editor")); return JsonStringify(Root); }
// BlueprintEditor inherits from FAssetEditorToolkit which inherits from IAssetEditorInstance.
// Cast guarded by editor-name check.
if (Inst->GetEditorName() != FName(TEXT("BlueprintEditor")))
{
Root->SetStringField(TEXT("error"), TEXT("active editor is not BlueprintEditor"));
return JsonStringify(Root);
}
FBlueprintEditor* BPE = static_cast<FBlueprintEditor*>(Inst);
FGraphPanelSelectionSet Sel = BPE->GetSelectedNodes();
TArray<TSharedPtr<FJsonValue>> Arr;
for (UObject* O : Sel)
{
if (UEdGraphNode* N = Cast<UEdGraphNode>(O))
{
Arr.Add(MakeShared<FJsonValueObject>(NodeToJson(N)));
}
}
Root->SetArrayField(TEXT("selected"), Arr);
// Active graph
if (UEdGraph* Focused = BPE->GetFocusedGraph())
{
Root->SetStringField(TEXT("focused_graph"), Focused->GetName());
}
return JsonStringify(Root);
}
TArray<FString> UMCPGraphLibrary::ListOpenBlueprints()
{
TArray<FString> Out;
if (!GEditor) return Out;
UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
if (!AES) return Out;
TArray<UObject*> Assets = AES->GetAllEditedAssets();
for (UObject* A : Assets)
if (UBlueprint* BP = Cast<UBlueprint>(A))
Out.Add(BP->GetPathName());
return Out;
}
FString UMCPGraphLibrary::ResolveFunctionSourceJson(UEdGraph* Graph, const FString& NodeId)
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) { R->SetStringField(TEXT("error"), TEXT("node not found")); return JsonStringify(R); }
UK2Node_CallFunction* CF = Cast<UK2Node_CallFunction>(N);
if (!CF) { R->SetStringField(TEXT("error"), TEXT("not a CallFunction node")); return JsonStringify(R); }
UClass* Owner = CF->FunctionReference.GetMemberParentClass();
FName Fn = CF->FunctionReference.GetMemberName();
R->SetStringField(TEXT("function"), Fn.ToString());
R->SetStringField(TEXT("owner_class"), Owner ? Owner->GetPathName() : TEXT("?"));
R->SetBoolField(TEXT("native"), Owner && Owner->HasAllClassFlags(CLASS_Native));
if (Owner && !Owner->HasAllClassFlags(CLASS_Native))
{
if (UBlueprint* SrcBP = Cast<UBlueprint>(Owner->ClassGeneratedBy))
{
R->SetStringField(TEXT("source_bp_path"), SrcBP->GetPathName());
UEdGraph* FG = nullptr;
for (UEdGraph* G : SrcBP->FunctionGraphs)
if (G && G->GetFName() == Fn) { FG = G; break; }
if (FG) R->SetStringField(TEXT("source_graph"), FG->GetName());
}
}
return JsonStringify(R);
}
static void CollectRefsToVariable(UBlueprint* BP, FName Name, TArray<TSharedPtr<FJsonValue>>& Out)
{
TArray<UEdGraph*> AllGraphs;
BP->GetAllGraphs(AllGraphs);
for (UEdGraph* G : AllGraphs)
{
if (!G) continue;
for (UEdGraphNode* N : G->Nodes)
{
UK2Node_VariableGet* VG = Cast<UK2Node_VariableGet>(N);
UK2Node_VariableSet* VS = Cast<UK2Node_VariableSet>(N);
FName MemberName = NAME_None;
if (VG) MemberName = VG->VariableReference.GetMemberName();
else if (VS) MemberName = VS->VariableReference.GetMemberName();
if (MemberName == Name)
{
TSharedRef<FJsonObject> J = MakeShared<FJsonObject>();
J->SetStringField(TEXT("graph"), G->GetName());
J->SetStringField(TEXT("guid"), N->NodeGuid.ToString());
J->SetStringField(TEXT("kind"), VG ? TEXT("get") : TEXT("set"));
Out.Add(MakeShared<FJsonValueObject>(J));
}
}
}
}
FString UMCPGraphLibrary::FindVariableReferencesJson(UBlueprint* Blueprint, FName VariableName)
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
if (!Blueprint) { R->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(R); }
TArray<TSharedPtr<FJsonValue>> Refs;
CollectRefsToVariable(Blueprint, VariableName, Refs);
R->SetArrayField(TEXT("references"), Refs);
R->SetNumberField(TEXT("count"), Refs.Num());
return JsonStringify(R);
}
FString UMCPGraphLibrary::FindFunctionReferencesJson(UBlueprint* Blueprint, FName FunctionName)
{
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
if (!Blueprint) { R->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(R); }
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
TArray<TSharedPtr<FJsonValue>> Refs;
for (UEdGraph* G : AllGraphs)
{
if (!G) continue;
for (UEdGraphNode* N : G->Nodes)
{
UK2Node_CallFunction* CF = Cast<UK2Node_CallFunction>(N);
if (!CF) continue;
if (CF->FunctionReference.GetMemberName() == FunctionName)
{
TSharedRef<FJsonObject> J = MakeShared<FJsonObject>();
J->SetStringField(TEXT("graph"), G->GetName());
J->SetStringField(TEXT("guid"), N->NodeGuid.ToString());
UClass* O = CF->FunctionReference.GetMemberParentClass();
J->SetStringField(TEXT("owner"), O ? O->GetPathName() : TEXT("?"));
Refs.Add(MakeShared<FJsonValueObject>(J));
}
}
}
R->SetArrayField(TEXT("references"), Refs);
R->SetNumberField(TEXT("count"), Refs.Num());
return JsonStringify(R);
}
TArray<FString> UMCPGraphLibrary::FindBlueprintsByParent(const FString& ParentClassPath)
{
TArray<FString> Out;
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
FARFilter Filter;
Filter.ClassPaths.Add(FTopLevelAssetPath(TEXT("/Script/Engine"), TEXT("Blueprint")));
Filter.bRecursiveClasses = true;
TArray<FAssetData> Assets;
AR.GetAssets(Filter, Assets);
for (const FAssetData& AD : Assets)
{
const FString Parent = AD.GetTagValueRef<FString>(FBlueprintTags::ParentClassPath);
if (Parent == ParentClassPath || Parent.Contains(ParentClassPath))
Out.Add(AD.GetSoftObjectPath().ToString());
}
return Out;
}
TArray<FString> UMCPGraphLibrary::GetReferencedAssets(const FString& AssetPath)
{
TArray<FString> Out;
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
TArray<FName> Deps;
AR.GetDependencies(FName(*FPackageName::ObjectPathToPackageName(AssetPath)), Deps);
for (FName D : Deps) Out.Add(D.ToString());
return Out;
}
TArray<FString> UMCPGraphLibrary::GetReferencersOfAsset(const FString& AssetPath)
{
TArray<FString> Out;
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
TArray<FName> Refs;
AR.GetReferencers(FName(*FPackageName::ObjectPathToPackageName(AssetPath)), Refs);
for (FName R : Refs) Out.Add(R.ToString());
return Out;
}
// ===========================================================================
// Editing v0.3
// ===========================================================================
int32 UMCPGraphLibrary::MoveNodes(UEdGraph* Graph, const TArray<FString>& NodeIds, const TArray<FVector2D>& Positions)
{
if (!Graph || NodeIds.Num() != Positions.Num()) return 0;
int32 Moved = 0;
for (int32 i = 0; i < NodeIds.Num(); ++i)
{
if (UEdGraphNode* N = FindNodeByGuid(Graph, NodeIds[i]))
{
N->NodePosX = static_cast<int32>(Positions[i].X);
N->NodePosY = static_cast<int32>(Positions[i].Y);
++Moved;
}
}
if (Moved && Graph)
if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP);
return Moved;
}
bool UMCPGraphLibrary::ResizeCommentNode(UEdGraph* Graph, const FString& NodeId, FVector2D Size)
{
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
UEdGraphNode_Comment* CN = Cast<UEdGraphNode_Comment>(N);
if (!CN) return false;
CN->NodeWidth = FMath::Max(64, (int32)Size.X);
CN->NodeHeight = FMath::Max(64, (int32)Size.Y);
return true;
}
bool UMCPGraphLibrary::SetNodeComment(UEdGraph* Graph, const FString& NodeId, const FString& Comment, bool bCommentBubbleVisible)
{
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) return false;
N->NodeComment = Comment;
N->bCommentBubbleVisible = bCommentBubbleVisible;
return true;
}
// ===========================================================================
// Navigation v0.3
// ===========================================================================
bool UMCPGraphLibrary::OpenBlueprintEditor(UBlueprint* Blueprint)
{
if (!Blueprint || !GEditor) return false;
UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
return AES && AES->OpenEditorForAsset(Blueprint);
}
bool UMCPGraphLibrary::OpenGraphInEditor(UBlueprint* Blueprint, UEdGraph* Graph)
{
if (!OpenBlueprintEditor(Blueprint) || !Graph) return false;
UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
IAssetEditorInstance* Inst = AES->FindEditorForAsset(Blueprint, true);
if (!Inst || Inst->GetEditorName() != FName(TEXT("BlueprintEditor"))) return false;
FBlueprintEditor* BPE = static_cast<FBlueprintEditor*>(Inst);
BPE->OpenDocument(Graph, FDocumentTracker::OpenNewDocument);
return true;
}
bool UMCPGraphLibrary::JumpToNodeInEditor(UBlueprint* Blueprint, UEdGraph* Graph, const FString& NodeId)
{
if (!OpenGraphInEditor(Blueprint, Graph)) return false;
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
if (!N) return false;
UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
IAssetEditorInstance* Inst = AES->FindEditorForAsset(Blueprint, true);
FBlueprintEditor* BPE = static_cast<FBlueprintEditor*>(Inst);
BPE->JumpToNode(N, /*bRequestRename*/ false);
return true;
}