596 lines
19 KiB
C++
596 lines
19 KiB
C++
#pragma once
|
|
|
|
#include "CoreMinimal.h"
|
|
#include "MCPHandler.h"
|
|
#include "MCPAssetFinder.h"
|
|
#include "MCPUtils.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "EdGraph/EdGraphNode.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "K2Node_CustomEvent.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "Kismet2/KismetEditorUtilities.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "MCPHandlers_Graphs.generated.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
|
|
UCLASS(meta=(ToolName="reparent_blueprint"))
|
|
class UMCPHandler_ReparentBlueprint : public UObject, public IMCPHandler
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
|
FString Blueprint;
|
|
|
|
UPROPERTY(meta=(Description="Name of the new parent class (C++ class name or Blueprint name)"))
|
|
FString NewParentClass;
|
|
|
|
virtual FString GetDescription() const override
|
|
{
|
|
return TEXT("Change a Blueprint's parent class. Accepts C++ class names or Blueprint names.");
|
|
}
|
|
|
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
|
{
|
|
// Load Blueprint
|
|
MCPAssets<UBlueprint> Assets;
|
|
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
|
|
UBlueprint* BP = Assets.Object();
|
|
|
|
FString OldParentName = BP->ParentClass ? BP->ParentClass->GetName() : TEXT("None");
|
|
|
|
// Find the new parent class
|
|
// Try C++ class first (e.g. "WebUIHUD" finds /Script/ModuleName.WebUIHUD)
|
|
UClass* NewParentClassObj = nullptr;
|
|
|
|
// Search across all packages for native classes
|
|
for (TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
if (It->GetName() == NewParentClass)
|
|
{
|
|
NewParentClassObj = *It;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If not found as C++ class, try loading as a Blueprint asset
|
|
if (!NewParentClassObj)
|
|
{
|
|
MCPAssets<UBlueprint> ParentAssets;
|
|
if (!ParentAssets.Exact(NewParentClass).AllContent().Errors(Result).ETwo().Load()) return;
|
|
if (!ParentAssets.Objects().IsEmpty())
|
|
{
|
|
if (ParentAssets.Object()->GeneratedClass)
|
|
NewParentClassObj = ParentAssets.Object()->GeneratedClass;
|
|
}
|
|
}
|
|
|
|
if (!NewParentClassObj)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Could not find class '%s'. Provide a C++ class name (e.g. 'WebUIHUD') or Blueprint name."),
|
|
*NewParentClass));
|
|
}
|
|
|
|
// Validate: new parent must be compatible
|
|
if (BP->ParentClass && !NewParentClassObj->IsChildOf(BP->ParentClass->GetSuperClass()) &&
|
|
BP->ParentClass != NewParentClassObj)
|
|
{
|
|
// Just warn, don't block — the user may intentionally reparent to a sibling
|
|
UE_LOG(LogTemp, Warning,
|
|
TEXT("BlueprintMCP: Reparenting '%s' from '%s' to '%s' — classes are not in a direct hierarchy"),
|
|
*Blueprint, *OldParentName, *NewParentClassObj->GetName());
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparenting '%s' from '%s' to '%s'"),
|
|
*Blueprint, *OldParentName, *NewParentClassObj->GetName());
|
|
|
|
// Perform reparent
|
|
BP->ParentClass = NewParentClassObj;
|
|
|
|
// Refresh all nodes to pick up new parent's functions/variables
|
|
FBlueprintEditorUtils::RefreshAllNodes(BP);
|
|
|
|
// Compile
|
|
FKismetEditorUtilities::CompileBlueprint(BP);
|
|
|
|
// Save
|
|
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
|
|
|
|
FString NewParentActualName = NewParentClassObj->GetName();
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Reparent complete, save %s"),
|
|
bSaved ? TEXT("succeeded") : TEXT("failed"));
|
|
|
|
Result->SetStringField(TEXT("oldParentClass"), OldParentName);
|
|
Result->SetStringField(TEXT("newParentClass"), NewParentActualName);
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
}
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
|
|
UCLASS(meta=(ToolName="create_blueprint_asset"))
|
|
class UMCPHandler_CreateBlueprint : public UObject, public IMCPHandler
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
UPROPERTY(meta=(Description="New Blueprint asset name"))
|
|
FString Blueprint;
|
|
|
|
UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)"))
|
|
FString PackagePath;
|
|
|
|
UPROPERTY(meta=(Description="Parent class name (C++ class name or Blueprint name)"))
|
|
FString ParentClass;
|
|
|
|
UPROPERTY(meta=(Optional, Description="Blueprint type: Normal, Interface, FunctionLibrary, or MacroLibrary (default: Normal)"))
|
|
FString BlueprintType;
|
|
|
|
virtual FString GetDescription() const override
|
|
{
|
|
return TEXT("Create a new Blueprint asset with a specified parent class and type.");
|
|
}
|
|
|
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
|
{
|
|
// Validate packagePath starts with /Game
|
|
if (!PackagePath.StartsWith(TEXT("/Game")))
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("packagePath must start with '/Game'"));
|
|
}
|
|
|
|
// Check if asset already exists
|
|
FString FullAssetPath = PackagePath / Blueprint;
|
|
MCPAssets<UBlueprint> ExistCheck;
|
|
if (!ExistCheck.Exact(Blueprint).Errors(Result).EAny().Info()) return;
|
|
|
|
// Resolve parent class — try C++ class first, then Blueprint
|
|
UClass* ParentClassObj = nullptr;
|
|
|
|
for (TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
if (It->GetName() == ParentClass)
|
|
{
|
|
ParentClassObj = *It;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!ParentClassObj)
|
|
{
|
|
MCPAssets<UBlueprint> ParentAssets;
|
|
if (!ParentAssets.Exact(ParentClass).AllContent().Errors(Result).ETwo().Load()) return;
|
|
if (!ParentAssets.Objects().IsEmpty())
|
|
{
|
|
if (ParentAssets.Object()->GeneratedClass)
|
|
ParentClassObj = ParentAssets.Object()->GeneratedClass;
|
|
}
|
|
}
|
|
|
|
if (!ParentClassObj)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Could not find parent class '%s'. Provide a C++ class name (e.g. 'Actor', 'Pawn') or Blueprint name."),
|
|
*ParentClass));
|
|
}
|
|
|
|
// Map blueprintType string to EBlueprintType
|
|
EBlueprintType BlueprintTypeEnum = BPTYPE_Normal;
|
|
if (!BlueprintType.IsEmpty())
|
|
{
|
|
if (BlueprintType == TEXT("Interface"))
|
|
{
|
|
BlueprintTypeEnum = BPTYPE_Interface;
|
|
}
|
|
else if (BlueprintType == TEXT("FunctionLibrary"))
|
|
{
|
|
BlueprintTypeEnum = BPTYPE_FunctionLibrary;
|
|
}
|
|
else if (BlueprintType == TEXT("MacroLibrary"))
|
|
{
|
|
BlueprintTypeEnum = BPTYPE_MacroLibrary;
|
|
}
|
|
else if (BlueprintType != TEXT("Normal"))
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Invalid blueprintType '%s'. Valid values: Normal, Interface, FunctionLibrary, MacroLibrary"),
|
|
*BlueprintType));
|
|
}
|
|
}
|
|
|
|
// For Interface type, parent must be UInterface
|
|
if ((BlueprintTypeEnum == BPTYPE_Interface) && !ParentClassObj->IsChildOf(UInterface::StaticClass()))
|
|
{
|
|
// Use the engine's standard BlueprintInterface parent
|
|
ParentClassObj = UInterface::StaticClass();
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating Blueprint '%s' in '%s' with parent '%s' (type=%s)"),
|
|
*Blueprint, *PackagePath, *ParentClassObj->GetName(), *BlueprintType);
|
|
|
|
// Create the package
|
|
FString FullPackagePath = PackagePath / Blueprint;
|
|
UPackage* Package = CreatePackage(*FullPackagePath);
|
|
if (!Package)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath));
|
|
}
|
|
|
|
// Create the Blueprint
|
|
UBlueprint* NewBP = FKismetEditorUtilities::CreateBlueprint(
|
|
ParentClassObj,
|
|
Package,
|
|
FName(*Blueprint),
|
|
BlueprintTypeEnum,
|
|
UBlueprint::StaticClass(),
|
|
UBlueprintGeneratedClass::StaticClass()
|
|
);
|
|
|
|
if (!NewBP)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("FKismetEditorUtilities::CreateBlueprint returned null"));
|
|
}
|
|
|
|
// Compile
|
|
FKismetEditorUtilities::CompileBlueprint(NewBP);
|
|
|
|
// Save
|
|
bool bSaved = MCPUtils::SaveBlueprintPackage(NewBP);
|
|
|
|
|
|
// Collect graph names
|
|
TArray<TSharedPtr<FJsonValue>> GraphNames;
|
|
for (UEdGraph* Graph : NewBP->UbergraphPages)
|
|
{
|
|
GraphNames.Add(MakeShared<FJsonValueString>(Graph->GetName()));
|
|
}
|
|
for (UEdGraph* Graph : NewBP->FunctionGraphs)
|
|
{
|
|
GraphNames.Add(MakeShared<FJsonValueString>(Graph->GetName()));
|
|
}
|
|
for (UEdGraph* Graph : NewBP->MacroGraphs)
|
|
{
|
|
GraphNames.Add(MakeShared<FJsonValueString>(Graph->GetName()));
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blueprint '%s' with %d graphs (saved: %s)"),
|
|
*Blueprint, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false"));
|
|
|
|
Result->SetStringField(TEXT("assetPath"), FullAssetPath);
|
|
Result->SetStringField(TEXT("parentClass"), ParentClassObj->GetName());
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
Result->SetArrayField(TEXT("graphs"), GraphNames);
|
|
}
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
|
|
UCLASS(meta=(ToolName="create_blueprint_graph"))
|
|
class UMCPHandler_CreateGraph : public UObject, public IMCPHandler
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
|
FString Blueprint;
|
|
|
|
UPROPERTY(meta=(Description="Name for the new graph"))
|
|
FString Graph;
|
|
|
|
UPROPERTY(meta=(Description="Type of graph: function, macro, or customEvent"))
|
|
FString GraphType;
|
|
|
|
virtual FString GetDescription() const override
|
|
{
|
|
return TEXT("Create a new function, macro, or custom event graph in a Blueprint.");
|
|
}
|
|
|
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
|
{
|
|
if (GraphType != TEXT("function") && GraphType != TEXT("macro") && GraphType != TEXT("customEvent"))
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Invalid graphType '%s'. Valid values: function, macro, customEvent"), *GraphType));
|
|
}
|
|
|
|
// Load Blueprint
|
|
MCPAssets<UBlueprint> Assets;
|
|
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
|
|
UBlueprint* BP = Assets.Object();
|
|
|
|
// Check graph name uniqueness
|
|
TArray<UEdGraph*> AllGraphs;
|
|
BP->GetAllGraphs(AllGraphs);
|
|
for (UEdGraph* Existing : AllGraphs)
|
|
{
|
|
if (Existing && Existing->GetName().Equals(Graph, ESearchCase::IgnoreCase))
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("A graph named '%s' already exists in Blueprint '%s'"), *Graph, *Blueprint));
|
|
}
|
|
}
|
|
|
|
// Also check for existing custom events with the same name
|
|
if (GraphType == TEXT("customEvent"))
|
|
{
|
|
for (UEdGraph* ExistingGraph : AllGraphs)
|
|
{
|
|
if (!ExistingGraph) continue;
|
|
for (UEdGraphNode* Node : ExistingGraph->Nodes)
|
|
{
|
|
if (UK2Node_CustomEvent* CE = Cast<UK2Node_CustomEvent>(Node))
|
|
{
|
|
if (CE->CustomFunctionName == FName(*Graph))
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("A custom event named '%s' already exists in Blueprint '%s'"), *Graph, *Blueprint));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Creating %s graph '%s' in Blueprint '%s'"),
|
|
*GraphType, *Graph, *Blueprint);
|
|
|
|
FString CreatedNodeId;
|
|
|
|
if (GraphType == TEXT("function"))
|
|
{
|
|
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, FName(*Graph),
|
|
UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
|
if (!NewGraph)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create function graph"));
|
|
}
|
|
FBlueprintEditorUtils::AddFunctionGraph(BP, NewGraph, /*bIsUserCreated=*/true, /*SignatureFromObject=*/static_cast<UClass*>(nullptr));
|
|
}
|
|
else if (GraphType == TEXT("macro"))
|
|
{
|
|
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, FName(*Graph),
|
|
UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
|
if (!NewGraph)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create macro graph"));
|
|
}
|
|
FBlueprintEditorUtils::AddMacroGraph(BP, NewGraph, /*bIsUserCreated=*/true, /*SignatureFromClass=*/nullptr);
|
|
}
|
|
else // customEvent
|
|
{
|
|
// Find the EventGraph (first UbergraphPage)
|
|
UEdGraph* EventGraph = nullptr;
|
|
if (BP->UbergraphPages.Num() > 0)
|
|
{
|
|
EventGraph = BP->UbergraphPages[0];
|
|
}
|
|
if (!EventGraph)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, TEXT("Blueprint has no EventGraph to add a custom event to"));
|
|
}
|
|
|
|
// Create a custom event node in the EventGraph
|
|
UK2Node_CustomEvent* NewEvent = NewObject<UK2Node_CustomEvent>(EventGraph);
|
|
NewEvent->CustomFunctionName = FName(*Graph);
|
|
NewEvent->bIsEditable = true;
|
|
EventGraph->AddNode(NewEvent, /*bFromUI=*/false, /*bSelectNewNode=*/false);
|
|
NewEvent->CreateNewGuid();
|
|
NewEvent->PostPlacedNewNode();
|
|
NewEvent->AllocateDefaultPins();
|
|
CreatedNodeId = NewEvent->NodeGuid.ToString();
|
|
}
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
|
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created %s graph '%s' in '%s' (saved: %s)"),
|
|
*GraphType, *Graph, *Blueprint, bSaved ? TEXT("true") : TEXT("false"));
|
|
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
if (!CreatedNodeId.IsEmpty())
|
|
{
|
|
Result->SetStringField(TEXT("nodeId"), CreatedNodeId);
|
|
}
|
|
}
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
|
|
UCLASS(meta=(ToolName="delete_blueprint_graph"))
|
|
class UMCPHandler_DeleteGraph : public UObject, public IMCPHandler
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
|
FString Blueprint;
|
|
|
|
UPROPERTY(meta=(Description="Name of the graph to delete"))
|
|
FString Graph;
|
|
|
|
virtual FString GetDescription() const override
|
|
{
|
|
return TEXT("Delete a function or macro graph from a Blueprint. Cannot delete EventGraph pages.");
|
|
}
|
|
|
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
|
{
|
|
MCPAssets<UBlueprint> Assets;
|
|
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
|
|
UBlueprint* BP = Assets.Object();
|
|
|
|
// Find the graph
|
|
UEdGraph* TargetGraph = nullptr;
|
|
FString GraphType;
|
|
|
|
for (UEdGraph* CandidateGraph : BP->FunctionGraphs)
|
|
{
|
|
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase))
|
|
{
|
|
TargetGraph = CandidateGraph;
|
|
GraphType = TEXT("function");
|
|
break;
|
|
}
|
|
}
|
|
if (!TargetGraph)
|
|
{
|
|
for (UEdGraph* CandidateGraph : BP->MacroGraphs)
|
|
{
|
|
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase))
|
|
{
|
|
TargetGraph = CandidateGraph;
|
|
GraphType = TEXT("macro");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check if it's an UbergraphPage (EventGraph) — disallow deletion
|
|
if (!TargetGraph)
|
|
{
|
|
for (UEdGraph* CandidateGraph : BP->UbergraphPages)
|
|
{
|
|
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase))
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Cannot delete UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be deleted."),
|
|
*Graph));
|
|
}
|
|
}
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *Graph, *Blueprint));
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleting %s graph '%s' from Blueprint '%s'"),
|
|
*GraphType, *Graph, *Blueprint);
|
|
|
|
// Count nodes for reporting
|
|
int32 NodeCount = TargetGraph->Nodes.Num();
|
|
|
|
// Remove the graph
|
|
FBlueprintEditorUtils::RemoveGraph(BP, TargetGraph, EGraphRemoveFlags::Default);
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
|
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Deleted graph '%s' (%d nodes), save %s"),
|
|
*Graph, NodeCount, bSaved ? TEXT("true") : TEXT("false"));
|
|
|
|
Result->SetStringField(TEXT("graphType"), GraphType);
|
|
Result->SetNumberField(TEXT("nodeCount"), NodeCount);
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
}
|
|
};
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
// ---------------------------------------------------------------------------
|
|
|
|
UCLASS(meta=(ToolName="rename_blueprint_graph"))
|
|
class UMCPHandler_RenameGraph : public UObject, public IMCPHandler
|
|
{
|
|
GENERATED_BODY()
|
|
|
|
public:
|
|
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
|
FString Blueprint;
|
|
|
|
UPROPERTY(meta=(Description="Current name of the graph to rename"))
|
|
FString Graph;
|
|
|
|
UPROPERTY(meta=(Description="New name for the graph"))
|
|
FString NewName;
|
|
|
|
virtual FString GetDescription() const override
|
|
{
|
|
return TEXT("Rename a function or macro graph in a Blueprint. Cannot rename EventGraph pages.");
|
|
}
|
|
|
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
|
{
|
|
MCPAssets<UBlueprint> Assets;
|
|
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
|
|
UBlueprint* BP = Assets.Object();
|
|
|
|
// Check if it's an UbergraphPage — disallow rename
|
|
for (UEdGraph* CandidateGraph : BP->UbergraphPages)
|
|
{
|
|
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase))
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Cannot rename UbergraphPage '%s'. EventGraph and other Ubergraph pages cannot be renamed."),
|
|
*Graph));
|
|
}
|
|
}
|
|
|
|
// Find the graph in FunctionGraphs or MacroGraphs
|
|
UEdGraph* TargetGraph = nullptr;
|
|
FString GraphType;
|
|
|
|
for (UEdGraph* CandidateGraph : BP->FunctionGraphs)
|
|
{
|
|
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase))
|
|
{
|
|
TargetGraph = CandidateGraph;
|
|
GraphType = TEXT("function");
|
|
break;
|
|
}
|
|
}
|
|
if (!TargetGraph)
|
|
{
|
|
for (UEdGraph* CandidateGraph : BP->MacroGraphs)
|
|
{
|
|
if (CandidateGraph && CandidateGraph->GetName().Equals(Graph, ESearchCase::IgnoreCase))
|
|
{
|
|
TargetGraph = CandidateGraph;
|
|
GraphType = TEXT("macro");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!TargetGraph)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found in Blueprint '%s'"), *Graph, *Blueprint));
|
|
}
|
|
|
|
// Check for name collision
|
|
TArray<UEdGraph*> AllGraphs;
|
|
BP->GetAllGraphs(AllGraphs);
|
|
for (UEdGraph* Existing : AllGraphs)
|
|
{
|
|
if (Existing && Existing != TargetGraph && Existing->GetName().Equals(NewName, ESearchCase::IgnoreCase))
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("A graph named '%s' already exists in Blueprint '%s'"), *NewName, *Blueprint));
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renaming %s graph '%s' to '%s' in Blueprint '%s'"),
|
|
*GraphType, *Graph, *NewName, *Blueprint);
|
|
|
|
FBlueprintEditorUtils::RenameGraph(TargetGraph, NewName);
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
|
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Renamed graph '%s' to '%s', save %s"),
|
|
*Graph, *NewName, bSaved ? TEXT("true") : TEXT("false"));
|
|
|
|
Result->SetStringField(TEXT("newName"), TargetGraph->GetName());
|
|
Result->SetStringField(TEXT("graphType"), GraphType);
|
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
}
|
|
};
|