258 lines
7.6 KiB
C++
258 lines
7.6 KiB
C++
#include "BlueprintMCPHandlers_Interfaces.h"
|
|
#include "BlueprintMCPServer.h"
|
|
#include "MCPUtils.h"
|
|
#include "Engine/Blueprint.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
|
|
// ============================================================
|
|
// ListInterfaces
|
|
// ============================================================
|
|
|
|
void UMCPHandler_ListBlueprintInterfaces::Handle(const FJsonObject* Json, FJsonObject* Result)
|
|
{
|
|
MCPHelper* Helper = MCPHelper::Get();
|
|
|
|
FString LoadError;
|
|
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
|
|
if (!BP)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
}
|
|
|
|
TArray<TSharedPtr<FJsonValue>> InterfacesArr;
|
|
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
|
|
{
|
|
if (!IfaceDesc.Interface)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TSharedRef<FJsonObject> IfaceObj = MakeShared<FJsonObject>();
|
|
IfaceObj->SetStringField(TEXT("name"), IfaceDesc.Interface->GetName());
|
|
IfaceObj->SetStringField(TEXT("classPath"), IfaceDesc.Interface->GetPathName());
|
|
|
|
TArray<TSharedPtr<FJsonValue>> FuncArr;
|
|
for (const UEdGraph* Graph : IfaceDesc.Graphs)
|
|
{
|
|
if (Graph)
|
|
{
|
|
FuncArr.Add(MakeShared<FJsonValueString>(Graph->GetName()));
|
|
}
|
|
}
|
|
IfaceObj->SetArrayField(TEXT("functions"), FuncArr);
|
|
|
|
InterfacesArr.Add(MakeShared<FJsonValueObject>(IfaceObj));
|
|
}
|
|
|
|
Result->SetStringField(TEXT("blueprint"), Blueprint);
|
|
Result->SetNumberField(TEXT("count"), InterfacesArr.Num());
|
|
Result->SetArrayField(TEXT("interfaces"), InterfacesArr);
|
|
}
|
|
|
|
// ============================================================
|
|
// AddInterface
|
|
// ============================================================
|
|
|
|
void UMCPHandler_AddBlueprintInterface::Handle(const FJsonObject* Json, FJsonObject* Result)
|
|
{
|
|
MCPHelper* Helper = MCPHelper::Get();
|
|
|
|
FString LoadError;
|
|
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
|
|
if (!BP)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
}
|
|
|
|
// Resolve the interface class
|
|
UClass* InterfaceClass = nullptr;
|
|
|
|
// Strategy 1: Search loaded UInterface classes by name
|
|
for (TObjectIterator<UClass> It; It; ++It)
|
|
{
|
|
if (!It->IsChildOf(UInterface::StaticClass()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FString ClassName = It->GetName();
|
|
// Match by class name (e.g. "BPI_Foo_C") or by trimmed name (e.g. "BPI_Foo")
|
|
if (ClassName.Equals(InterfaceName, ESearchCase::IgnoreCase))
|
|
{
|
|
InterfaceClass = *It;
|
|
break;
|
|
}
|
|
// Strip the generated "_C" suffix for comparison
|
|
FString TrimmedName = ClassName;
|
|
if (TrimmedName.EndsWith(TEXT("_C")))
|
|
{
|
|
TrimmedName = TrimmedName.LeftChop(2);
|
|
}
|
|
if (TrimmedName.Equals(InterfaceName, ESearchCase::IgnoreCase))
|
|
{
|
|
InterfaceClass = *It;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Strategy 2: Try loading as a Blueprint Interface asset
|
|
if (!InterfaceClass)
|
|
{
|
|
FString IfaceLoadError;
|
|
UBlueprint* IfaceBP = Helper->LoadBlueprintByName(InterfaceName, IfaceLoadError);
|
|
if (IfaceBP && IfaceBP->GeneratedClass && IfaceBP->GeneratedClass->IsChildOf(UInterface::StaticClass()))
|
|
{
|
|
InterfaceClass = IfaceBP->GeneratedClass;
|
|
}
|
|
}
|
|
|
|
if (!InterfaceClass)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Interface '%s' not found. Provide a Blueprint Interface asset name (e.g. 'BPI_MyInterface') or a native UInterface class name."),
|
|
*InterfaceName));
|
|
}
|
|
|
|
// Check for duplicates
|
|
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
|
|
{
|
|
if (IfaceDesc.Interface == InterfaceClass)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Interface '%s' is already implemented by Blueprint '%s'"),
|
|
*InterfaceName, *Blueprint));
|
|
}
|
|
}
|
|
|
|
FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName();
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding interface '%s' to Blueprint '%s'"),
|
|
*InterfaceClass->GetName(), *Blueprint);
|
|
|
|
bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath);
|
|
if (!bAdded)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("FBlueprintEditorUtils::ImplementNewInterface failed for interface '%s' on Blueprint '%s'"),
|
|
*InterfaceName, *Blueprint));
|
|
}
|
|
|
|
// Collect stub function graph names from the newly added interface entry
|
|
TArray<FString> AddedFunctions;
|
|
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
|
|
{
|
|
if (IfaceDesc.Interface == InterfaceClass)
|
|
{
|
|
for (const UEdGraph* Graph : IfaceDesc.Graphs)
|
|
{
|
|
if (Graph)
|
|
{
|
|
AddedFunctions.Add(Graph->GetName());
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added interface '%s' to '%s' (%d function stubs)"),
|
|
*InterfaceClass->GetName(), *Blueprint, AddedFunctions.Num());
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
Result->SetStringField(TEXT("blueprint"), Blueprint);
|
|
Result->SetStringField(TEXT("interfaceName"), InterfaceClass->GetName());
|
|
Result->SetStringField(TEXT("interfacePath"), InterfaceClass->GetPathName());
|
|
|
|
TArray<TSharedPtr<FJsonValue>> FuncArr;
|
|
for (const FString& FuncName : AddedFunctions)
|
|
{
|
|
FuncArr.Add(MakeShared<FJsonValueString>(FuncName));
|
|
}
|
|
Result->SetArrayField(TEXT("functionGraphsAdded"), FuncArr);
|
|
}
|
|
|
|
// ============================================================
|
|
// RemoveInterface
|
|
// ============================================================
|
|
|
|
void UMCPHandler_RemoveBlueprintInterface::Handle(const FJsonObject* Json, FJsonObject* Result)
|
|
{
|
|
MCPHelper* Helper = MCPHelper::Get();
|
|
|
|
FString LoadError;
|
|
UBlueprint* BP = Helper->LoadBlueprintByName(Blueprint, LoadError);
|
|
if (!BP)
|
|
{
|
|
return MCPUtils::MakeErrorJson(Result, LoadError);
|
|
}
|
|
|
|
// Find the interface in ImplementedInterfaces by name (case-insensitive)
|
|
UClass* FoundInterface = nullptr;
|
|
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
|
|
{
|
|
if (!IfaceDesc.Interface)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FString ClassName = IfaceDesc.Interface->GetName();
|
|
if (ClassName.Equals(InterfaceName, ESearchCase::IgnoreCase))
|
|
{
|
|
FoundInterface = IfaceDesc.Interface;
|
|
break;
|
|
}
|
|
// Strip "_C" suffix for comparison
|
|
FString TrimmedName = ClassName;
|
|
if (TrimmedName.EndsWith(TEXT("_C")))
|
|
{
|
|
TrimmedName = TrimmedName.LeftChop(2);
|
|
}
|
|
if (TrimmedName.Equals(InterfaceName, ESearchCase::IgnoreCase))
|
|
{
|
|
FoundInterface = IfaceDesc.Interface;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!FoundInterface)
|
|
{
|
|
// Build helpful error with list of implemented interfaces
|
|
TArray<TSharedPtr<FJsonValue>> IfaceList;
|
|
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
|
|
{
|
|
if (IfaceDesc.Interface)
|
|
{
|
|
IfaceList.Add(MakeShared<FJsonValueString>(IfaceDesc.Interface->GetName()));
|
|
}
|
|
}
|
|
|
|
MCPUtils::MakeErrorJson(Result, FString::Printf(
|
|
TEXT("Interface '%s' is not implemented by Blueprint '%s'"),
|
|
*InterfaceName, *Blueprint));
|
|
Result->SetArrayField(TEXT("implementedInterfaces"), IfaceList);
|
|
return;
|
|
}
|
|
|
|
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removing interface '%s' from Blueprint '%s' (preserveFunctions: %s)"),
|
|
*FoundInterface->GetName(), *Blueprint, PreserveFunctions ? TEXT("true") : TEXT("false"));
|
|
|
|
FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions);
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
|
|
|
|
|
|
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed interface '%s' from '%s'"),
|
|
*FoundInterface->GetName(), *Blueprint);
|
|
|
|
Result->SetBoolField(TEXT("success"), true);
|
|
Result->SetStringField(TEXT("blueprint"), Blueprint);
|
|
Result->SetStringField(TEXT("interfaceName"), FoundInterface->GetName());
|
|
Result->SetBoolField(TEXT("preservedFunctions"), PreserveFunctions);
|
|
}
|