More MCP work
This commit is contained in:
@@ -1,248 +1,12 @@
|
||||
#include "MCPAssetFinder.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/Level.h"
|
||||
#include "Engine/LevelScriptBlueprint.h"
|
||||
#include "Materials/Material.h"
|
||||
#include "Materials/MaterialInstanceConstant.h"
|
||||
#include "Materials/MaterialFunction.h"
|
||||
#include "Animation/Skeleton.h"
|
||||
#include "Animation/AnimSequence.h"
|
||||
#include "Animation/BlendSpace.h"
|
||||
#include "Animation/AnimBlueprint.h"
|
||||
#include "AnimationStateMachineGraph.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "AssetRegistry/IAssetRegistry.h"
|
||||
|
||||
const TArray<FAssetData> UMCPAssetFinder::EmptyAssetArray;
|
||||
|
||||
// ============================================================
|
||||
// Initialize / Deinitialize — subscribe to asset registry events
|
||||
// ============================================================
|
||||
|
||||
void UMCPAssetFinder::Initialize(FSubsystemCollectionBase& Collection)
|
||||
{
|
||||
Super::Initialize(Collection);
|
||||
|
||||
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
|
||||
AR.OnAssetAdded().AddUObject(this, &UMCPAssetFinder::OnAssetEvent);
|
||||
AR.OnAssetRemoved().AddUObject(this, &UMCPAssetFinder::OnAssetEvent);
|
||||
AR.OnAssetUpdated().AddUObject(this, &UMCPAssetFinder::OnAssetEvent);
|
||||
AR.OnAssetRenamed().AddUObject(this, &UMCPAssetFinder::OnAssetRenamed);
|
||||
}
|
||||
|
||||
void UMCPAssetFinder::Deinitialize()
|
||||
{
|
||||
IAssetRegistry* AR = IAssetRegistry::Get();
|
||||
if (AR)
|
||||
{
|
||||
AR->OnAssetAdded().RemoveAll(this);
|
||||
AR->OnAssetRemoved().RemoveAll(this);
|
||||
AR->OnAssetUpdated().RemoveAll(this);
|
||||
AR->OnAssetRenamed().RemoveAll(this);
|
||||
}
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Get / Refresh
|
||||
// ============================================================
|
||||
|
||||
UMCPAssetFinder* UMCPAssetFinder::Get()
|
||||
{
|
||||
UMCPAssetFinder* Self = GEngine ? GEngine->GetEngineSubsystem<UMCPAssetFinder>() : nullptr;
|
||||
checkf(Self, TEXT("MCPAssetFinder::Get() called before engine initialization"));
|
||||
return Self;
|
||||
}
|
||||
|
||||
void UMCPAssetFinder::CacheAssets(IAssetRegistry &Registry, UClass *Class, bool IncludeSubclasses)
|
||||
{
|
||||
FName Key = Class->GetFName();
|
||||
TArray<FAssetData>& List = AssetCache.Add(Key);
|
||||
Registry.GetAssetsByClass(Class->GetClassPathName(), List, IncludeSubclasses);
|
||||
}
|
||||
|
||||
void UMCPAssetFinder::Refresh()
|
||||
{
|
||||
checkf(IsInGameThread(), TEXT("MCPAssetFinder must only be accessed from the game thread"));
|
||||
UMCPAssetFinder* Self = Get();
|
||||
|
||||
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
|
||||
|
||||
while (AR.IsLoadingAssets()) FPlatformProcess::Sleep(0.1f);
|
||||
|
||||
if (!Self->bDirty) return;
|
||||
|
||||
Self->AssetCache.Empty();
|
||||
|
||||
// Cache all asset classes.
|
||||
Self->CacheAssets(AR, UBlueprint::StaticClass(), true);
|
||||
Self->CacheAssets(AR, UWorld::StaticClass(), false);
|
||||
Self->CacheAssets(AR, UMaterial::StaticClass(), false);
|
||||
Self->CacheAssets(AR, UMaterialInstanceConstant::StaticClass(), false);
|
||||
Self->CacheAssets(AR, UMaterialFunction::StaticClass(), false);
|
||||
Self->CacheAssets(AR, UUserDefinedStruct::StaticClass(), false);
|
||||
Self->CacheAssets(AR, UUserDefinedEnum::StaticClass(), false);
|
||||
Self->CacheAssets(AR, USkeleton::StaticClass(), false);
|
||||
Self->CacheAssets(AR, UAnimSequence::StaticClass(), false);
|
||||
Self->CacheAssets(AR, UBlendSpace::StaticClass(), false);
|
||||
Self->CacheAssets(AR, UAnimBlueprint::StaticClass(), false);
|
||||
|
||||
// Combined list: blueprints + maps
|
||||
TArray<FAssetData>& Combined = Self->AssetCache.Add(BlueprintsAndMaps);
|
||||
Combined = Self->AssetCache[UBlueprint::StaticClass()->GetFName()];
|
||||
Combined.Append(Self->AssetCache[UWorld::StaticClass()->GetFName()]);
|
||||
|
||||
Self->bDirty = false;
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("MCPAssetFinder: Refreshed — %d asset types cached"),
|
||||
Self->AssetCache.Num());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Static asset list accessors
|
||||
// ============================================================
|
||||
|
||||
const TArray<FAssetData>& UMCPAssetFinder::GetAssets(FName Key)
|
||||
{
|
||||
TArray<FAssetData>* Found = Get()->AssetCache.Find(Key);
|
||||
return Found ? *Found : EmptyAssetArray;
|
||||
}
|
||||
|
||||
const TArray<FAssetData>& UMCPAssetFinder::GetAssets(UClass* Class)
|
||||
{
|
||||
return GetAssets(Class->GetFName());
|
||||
}
|
||||
|
||||
FName UMCPAssetFinder::BlueprintsAndMaps = FName(TEXT("BlueprintsAndMaps"));
|
||||
|
||||
// ============================================================
|
||||
// Find / Search helpers
|
||||
// ============================================================
|
||||
|
||||
FAssetData* UMCPAssetFinder::FindAsset(FName Class, const FString& NameOrPath, FString* OutError)
|
||||
{
|
||||
const TArray<FAssetData>& List = GetAssets(Class);
|
||||
bool IsPath = NameOrPath.Contains(TEXT("/"));
|
||||
|
||||
FAssetData* Found = nullptr;
|
||||
for (const FAssetData& Asset : List)
|
||||
{
|
||||
FName Name = IsPath ? Asset.PackageName : Asset.AssetName;
|
||||
if (!Name.ToString().Equals(NameOrPath, ESearchCase::IgnoreCase)) continue;
|
||||
if (!Found)
|
||||
{
|
||||
Found = const_cast<FAssetData*>(&Asset);
|
||||
continue;
|
||||
}
|
||||
if (OutError)
|
||||
{
|
||||
*OutError = FString::Printf(
|
||||
TEXT("Ambiguous asset name '%s' — matches both '%s' and '%s'. Use the full package path to disambiguate."),
|
||||
*NameOrPath, *Found->PackageName.ToString(), *Asset.PackageName.ToString());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return Found;
|
||||
}
|
||||
|
||||
FAssetData* UMCPAssetFinder::FindAsset(UClass* Class, const FString& NameOrPath, FString* OutError)
|
||||
{
|
||||
return FindAsset(Class->GetFName(), NameOrPath, OutError);
|
||||
}
|
||||
|
||||
TArray<FAssetData*> UMCPAssetFinder::SearchAssets(FName Class, const FString& Filter, FString* OutError)
|
||||
{
|
||||
const TArray<FAssetData>& List = GetAssets(Class);
|
||||
TArray<FAssetData*> Results;
|
||||
for (const FAssetData& Asset : List)
|
||||
{
|
||||
FString AssetName = Asset.AssetName.ToString();
|
||||
FString PackagePath = Asset.PackageName.ToString();
|
||||
if (AssetName.Contains(Filter, ESearchCase::IgnoreCase) ||
|
||||
PackagePath.Contains(Filter, ESearchCase::IgnoreCase))
|
||||
{
|
||||
Results.Add(const_cast<FAssetData*>(&Asset));
|
||||
}
|
||||
}
|
||||
return Results;
|
||||
}
|
||||
|
||||
TArray<FAssetData*> UMCPAssetFinder::SearchAssets(UClass* Class, const FString& Filter, FString* OutError)
|
||||
{
|
||||
return SearchAssets(Class->GetFName(), Filter, OutError);
|
||||
}
|
||||
|
||||
FAssetData* UMCPAssetFinder::FindAnyAsset(const FString& NameOrPath, FString* OutError)
|
||||
{
|
||||
FAssetData* Found = nullptr;
|
||||
|
||||
for (auto& Pair : Get()->AssetCache)
|
||||
{
|
||||
if (Pair.Key == BlueprintsAndMaps) continue;
|
||||
|
||||
FString LocalError;
|
||||
FAssetData* Asset = FindAsset(Pair.Key, NameOrPath, &LocalError);
|
||||
if (!LocalError.IsEmpty())
|
||||
{
|
||||
if (OutError) *OutError = LocalError;
|
||||
return nullptr;
|
||||
}
|
||||
if (!Asset) continue;
|
||||
if (Found)
|
||||
{
|
||||
if (OutError)
|
||||
{
|
||||
*OutError = FString::Printf(
|
||||
TEXT("Ambiguous asset name '%s' — matches '%s' and '%s'. Use the full package path to disambiguate."),
|
||||
*NameOrPath, *Found->PackageName.ToString(), *Asset->PackageName.ToString());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
Found = Asset;
|
||||
}
|
||||
return Found;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Load helpers
|
||||
// ============================================================
|
||||
|
||||
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(FAssetData& Asset, MCPErrorCallback Error)
|
||||
{
|
||||
// Regular blueprint asset
|
||||
UBlueprint* BP = Cast<UBlueprint>(Asset.GetAsset());
|
||||
if (BP) return BP;
|
||||
|
||||
// Map asset — extract the level blueprint
|
||||
UWorld* World = Cast<UWorld>(Asset.GetAsset());
|
||||
if (World && World->PersistentLevel)
|
||||
{
|
||||
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
|
||||
if (LevelBP) return LevelBP;
|
||||
}
|
||||
|
||||
Error.SetError(FString::Printf(TEXT("Asset '%s' loaded but its level blueprint could not be retrieved."),
|
||||
*Asset.AssetName.ToString()));
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UBlueprint* UMCPAssetFinder::LoadBlueprintOrLevelBlueprint(const FString& NameOrPath, MCPErrorCallback Error)
|
||||
{
|
||||
FString FindError;
|
||||
FAssetData* Asset = FindAsset(BlueprintsAndMaps, NameOrPath, &FindError);
|
||||
if (!Asset)
|
||||
{
|
||||
if (FindError.IsEmpty())
|
||||
FindError = FString::Printf(TEXT("Blueprint '%s' not found."), *NameOrPath);
|
||||
Error.SetError(FindError);
|
||||
return nullptr;
|
||||
}
|
||||
return LoadBlueprintOrLevelBlueprint(*Asset, Error);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// MCPAssetsBase
|
||||
// ============================================================
|
||||
@@ -263,7 +27,7 @@ MCPAssetsBase& MCPAssetsBase::Exact(const FString& InName)
|
||||
MCPAssetsBase& MCPAssetsBase::Substring(const FString& InFilter)
|
||||
{
|
||||
MatchName = InFilter;
|
||||
bExactMatch = false;
|
||||
bExactMatch = false;
|
||||
bPatternHasSlash = MatchName.Contains(TEXT("/"));
|
||||
return *this;
|
||||
}
|
||||
@@ -411,20 +175,3 @@ void MCPAssetsBase::SetError(const FString &Msg)
|
||||
UObjectResults.Empty();
|
||||
ErrorCB.SetError(Msg);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Load helpers
|
||||
// ============================================================
|
||||
|
||||
UAnimationStateMachineGraph* UMCPAssetFinder::LoadAnimStateMachineGraph(
|
||||
const FString& BlueprintName, const FString& GraphName, MCPErrorCallback Error)
|
||||
{
|
||||
UAnimBlueprint* AnimBP = LoadAsset<UAnimBlueprint>(BlueprintName, Error);
|
||||
if (!AnimBP) return nullptr;
|
||||
|
||||
UAnimationStateMachineGraph* SMGraph = MCPUtils::FindStateMachineGraph(AnimBP, GraphName);
|
||||
if (!SMGraph)
|
||||
Error.SetError(FString::Printf(TEXT("State machine graph '%s' not found in '%s'"), *GraphName, *BlueprintName));
|
||||
return SMGraph;
|
||||
}
|
||||
|
||||
|
||||
@@ -118,17 +118,7 @@ public:
|
||||
bool bSaved = MCPUtils::SaveBlueprintPackage(NewAnimBP);
|
||||
|
||||
|
||||
// Collect graph names
|
||||
TArray<TSharedPtr<FJsonValue>> GraphNames;
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
NewAnimBP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
{
|
||||
if (Graph)
|
||||
{
|
||||
GraphNames.Add(MakeShared<FJsonValueString>(Graph->GetName()));
|
||||
}
|
||||
}
|
||||
TArray<TSharedPtr<FJsonValue>> GraphNames = MCPUtils::AllGraphNamesJson(NewAnimBP);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created AnimBlueprint '%s' with %d graphs (saved: %s)"),
|
||||
*Name, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false"));
|
||||
@@ -172,25 +162,17 @@ public:
|
||||
|
||||
// Walk all anim nodes to collect slot names
|
||||
TSet<FString> SlotNames;
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
AnimBP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UAnimGraphNode_Base* AnimNode : MCPUtils::AllNodes<UAnimGraphNode_Base>(AnimBP))
|
||||
{
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
// Check for SlotName property via reflection
|
||||
for (TFieldIterator<FNameProperty> PropIt(AnimNode->GetClass()); PropIt; ++PropIt)
|
||||
{
|
||||
if (UAnimGraphNode_Base* AnimNode = Cast<UAnimGraphNode_Base>(Node))
|
||||
if (PropIt->GetName().Contains(TEXT("SlotName")) || PropIt->GetName().Contains(TEXT("Slot")))
|
||||
{
|
||||
// Check for SlotName property via reflection
|
||||
for (TFieldIterator<FNameProperty> PropIt(AnimNode->GetClass()); PropIt; ++PropIt)
|
||||
FName SlotValue = PropIt->GetPropertyValue_InContainer(AnimNode);
|
||||
if (!SlotValue.IsNone())
|
||||
{
|
||||
if (PropIt->GetName().Contains(TEXT("SlotName")) || PropIt->GetName().Contains(TEXT("Slot")))
|
||||
{
|
||||
FName SlotValue = PropIt->GetPropertyValue_InContainer(AnimNode);
|
||||
if (!SlotValue.IsNone())
|
||||
{
|
||||
SlotNames.Add(SlotValue.ToString());
|
||||
}
|
||||
}
|
||||
SlotNames.Add(SlotValue.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -238,25 +220,17 @@ public:
|
||||
|
||||
// Walk all anim nodes to collect sync group names
|
||||
TSet<FString> SyncGroupNames;
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
AnimBP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UAnimGraphNode_Base* AnimNode : MCPUtils::AllNodes<UAnimGraphNode_Base>(AnimBP))
|
||||
{
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
// Check for SyncGroup/GroupName property via reflection
|
||||
for (TFieldIterator<FNameProperty> PropIt(AnimNode->GetClass()); PropIt; ++PropIt)
|
||||
{
|
||||
if (UAnimGraphNode_Base* AnimNode = Cast<UAnimGraphNode_Base>(Node))
|
||||
if (PropIt->GetName().Contains(TEXT("SyncGroup")) || PropIt->GetName().Contains(TEXT("GroupName")))
|
||||
{
|
||||
// Check for SyncGroup/GroupName property via reflection
|
||||
for (TFieldIterator<FNameProperty> PropIt(AnimNode->GetClass()); PropIt; ++PropIt)
|
||||
FName GroupValue = PropIt->GetPropertyValue_InContainer(AnimNode);
|
||||
if (!GroupValue.IsNone())
|
||||
{
|
||||
if (PropIt->GetName().Contains(TEXT("SyncGroup")) || PropIt->GetName().Contains(TEXT("GroupName")))
|
||||
{
|
||||
FName GroupValue = PropIt->GetPropertyValue_InContainer(AnimNode);
|
||||
if (!GroupValue.IsNone())
|
||||
{
|
||||
SyncGroupNames.Add(GroupValue.ToString());
|
||||
}
|
||||
}
|
||||
SyncGroupNames.Add(GroupValue.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,16 +57,11 @@ public:
|
||||
}
|
||||
|
||||
// Check against existing graphs (functions, macros, etc.)
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* Existing : AllGraphs)
|
||||
if (!MCPUtils::AllGraphsNamed(BP, DispatcherName).IsEmpty())
|
||||
{
|
||||
if (Existing && Existing->GetName().Equals(DispatcherName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
||||
TEXT("A graph named '%s' already exists in Blueprint '%s'"),
|
||||
*DispatcherName, *Blueprint));
|
||||
}
|
||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
||||
TEXT("A graph named '%s' already exists in Blueprint '%s'"),
|
||||
*DispatcherName, *Blueprint));
|
||||
}
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Adding event dispatcher '%s' to Blueprint '%s'"),
|
||||
@@ -106,13 +101,10 @@ public:
|
||||
{
|
||||
// Find the entry node in the signature graph
|
||||
UK2Node_EditablePinBase* EntryNode = nullptr;
|
||||
for (UEdGraphNode* Node : SigGraph->Nodes)
|
||||
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(SigGraph))
|
||||
{
|
||||
if (UK2Node_FunctionEntry* FE = Cast<UK2Node_FunctionEntry>(Node))
|
||||
{
|
||||
EntryNode = FE;
|
||||
break;
|
||||
}
|
||||
EntryNode = FE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!EntryNode)
|
||||
@@ -197,11 +189,8 @@ public:
|
||||
UEdGraph* SigGraph = FBlueprintEditorUtils::GetDelegateSignatureGraphByName(BP, DelegateName);
|
||||
if (SigGraph)
|
||||
{
|
||||
for (UEdGraphNode* Node : SigGraph->Nodes)
|
||||
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(SigGraph))
|
||||
{
|
||||
UK2Node_FunctionEntry* FE = Cast<UK2Node_FunctionEntry>(Node);
|
||||
if (!FE) continue;
|
||||
|
||||
for (const TSharedPtr<FUserPinInfo>& PinInfo : FE->UserDefinedPins)
|
||||
{
|
||||
if (!PinInfo.IsValid()) continue;
|
||||
|
||||
@@ -248,19 +248,7 @@ public:
|
||||
|
||||
|
||||
// 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()));
|
||||
}
|
||||
TArray<TSharedPtr<FJsonValue>> GraphNames = MCPUtils::AllGraphNamesJson(NewBP);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Blueprint '%s' with %d graphs (saved: %s)"),
|
||||
*Blueprint, GraphNames.Num(), bSaved ? TEXT("true") : TEXT("false"));
|
||||
@@ -310,33 +298,21 @@ public:
|
||||
UBlueprint* BP = Assets.Object();
|
||||
|
||||
// Check graph name uniqueness
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* Existing : AllGraphs)
|
||||
if (!MCPUtils::AllGraphsNamed(BP, Graph).IsEmpty())
|
||||
{
|
||||
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));
|
||||
}
|
||||
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)
|
||||
for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP))
|
||||
{
|
||||
if (!ExistingGraph) continue;
|
||||
for (UEdGraphNode* Node : ExistingGraph->Nodes)
|
||||
if (CE->CustomFunctionName == FName(*Graph))
|
||||
{
|
||||
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));
|
||||
}
|
||||
}
|
||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
||||
TEXT("A custom event named '%s' already exists in Blueprint '%s'"), *Graph, *Blueprint));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -566,11 +542,9 @@ public:
|
||||
}
|
||||
|
||||
// Check for name collision
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* Existing : AllGraphs)
|
||||
for (UEdGraph* Existing : MCPUtils::AllGraphsNamed(BP, NewName))
|
||||
{
|
||||
if (Existing && Existing != TargetGraph && Existing->GetName().Equals(NewName, ESearchCase::IgnoreCase))
|
||||
if (Existing != TargetGraph)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(
|
||||
TEXT("A graph named '%s' already exists in Blueprint '%s'"), *NewName, *Blueprint));
|
||||
|
||||
@@ -180,23 +180,12 @@ public:
|
||||
|
||||
// Find the target graph
|
||||
FString DecodedGraphName = MCPUtils::UrlDecode(Graph);
|
||||
UEdGraph* TargetGraph = nullptr;
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
|
||||
for (UEdGraph* G : AllGraphs)
|
||||
{
|
||||
if (G && G->GetName().Equals(DecodedGraphName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
TargetGraph = G;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TargetGraph)
|
||||
TArray<UEdGraph*> MatchingGraphs = MCPUtils::AllGraphsNamed(BP, DecodedGraphName);
|
||||
if (MatchingGraphs.Num() == 0)
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName));
|
||||
}
|
||||
UEdGraph* TargetGraph = MatchingGraphs[0];
|
||||
|
||||
if (Nodes.Array.Num() == 0)
|
||||
{
|
||||
@@ -350,30 +339,19 @@ public:
|
||||
|
||||
// Find the target graph
|
||||
FString DecodedGraphName = MCPUtils::UrlDecode(Graph);
|
||||
UEdGraph* TargetGraph = nullptr;
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
|
||||
for (UEdGraph* G : AllGraphs)
|
||||
{
|
||||
if (G && G->GetName().Equals(DecodedGraphName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
TargetGraph = G;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!TargetGraph)
|
||||
TArray<UEdGraph*> MatchingGraphs = MCPUtils::AllGraphsNamed(BP, DecodedGraphName);
|
||||
if (MatchingGraphs.Num() == 0)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> GraphNames;
|
||||
for (UEdGraph* G : AllGraphs)
|
||||
for (UEdGraph* G : MCPUtils::AllGraphs(BP))
|
||||
{
|
||||
if (G) GraphNames.Add(MakeShared<FJsonValueString>(G->GetName()));
|
||||
GraphNames.Add(MakeShared<FJsonValueString>(G->GetName()));
|
||||
}
|
||||
MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName));
|
||||
Result->SetArrayField(TEXT("availableGraphs"), GraphNames);
|
||||
return;
|
||||
}
|
||||
UEdGraph* TargetGraph = MatchingGraphs[0];
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> Results;
|
||||
int32 SuccessCount = 0;
|
||||
@@ -713,13 +691,10 @@ public:
|
||||
*Blueprint, *OldClass, *NewClass, *NewClassPtr->GetPathName());
|
||||
|
||||
// Find all CallFunction nodes
|
||||
TArray<UK2Node_CallFunction*> AllCallNodes;
|
||||
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_CallFunction>(BP, AllCallNodes);
|
||||
|
||||
int32 ReplacedCount = 0;
|
||||
TArray<TSharedPtr<FJsonValue>> BrokenConnections;
|
||||
|
||||
for (UK2Node_CallFunction* CallNode : AllCallNodes)
|
||||
for (UK2Node_CallFunction* CallNode : MCPUtils::AllNodes<UK2Node_CallFunction>(BP))
|
||||
{
|
||||
UClass* ParentClass = CallNode->FunctionReference.GetMemberParentClass();
|
||||
if (!ParentClass)
|
||||
@@ -920,14 +895,8 @@ public:
|
||||
UBlueprint* BP = Assets.Object();
|
||||
|
||||
// Count graphs and nodes before refresh
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
int32 GraphCount = AllGraphs.Num();
|
||||
int32 NodeCount = 0;
|
||||
for (UEdGraph* G : AllGraphs)
|
||||
{
|
||||
if (G) NodeCount += G->Nodes.Num();
|
||||
}
|
||||
int32 GraphCount = MCPUtils::AllGraphs(BP).Num();
|
||||
int32 NodeCount = MCPUtils::AllNodes(BP).Num();
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Refreshing all nodes in '%s' (%d graphs, %d nodes)"),
|
||||
*Blueprint, GraphCount, NodeCount);
|
||||
@@ -937,21 +906,16 @@ public:
|
||||
|
||||
// Remove orphaned pins from all nodes
|
||||
int32 OrphanedPinsRemoved = 0;
|
||||
for (UEdGraph* G : AllGraphs)
|
||||
for (UEdGraphNode* Node : MCPUtils::AllNodes(BP))
|
||||
{
|
||||
if (!G) continue;
|
||||
for (UEdGraphNode* Node : G->Nodes)
|
||||
for (int32 i = Node->Pins.Num() - 1; i >= 0; --i)
|
||||
{
|
||||
if (!Node) continue;
|
||||
for (int32 i = Node->Pins.Num() - 1; i >= 0; --i)
|
||||
UEdGraphPin* Pin = Node->Pins[i];
|
||||
if (Pin && Pin->bOrphanedPin)
|
||||
{
|
||||
UEdGraphPin* Pin = Node->Pins[i];
|
||||
if (Pin && Pin->bOrphanedPin)
|
||||
{
|
||||
Pin->BreakAllPinLinks();
|
||||
Node->Pins.RemoveAt(i);
|
||||
OrphanedPinsRemoved++;
|
||||
}
|
||||
Pin->BreakAllPinLinks();
|
||||
Node->Pins.RemoveAt(i);
|
||||
OrphanedPinsRemoved++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -976,27 +940,20 @@ public:
|
||||
}
|
||||
|
||||
// Check each graph for nodes with error/warning status
|
||||
AllGraphs.Empty();
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* G : AllGraphs)
|
||||
for (UEdGraphNode* Node : MCPUtils::AllNodes(BP))
|
||||
{
|
||||
if (!G) continue;
|
||||
for (UEdGraphNode* Node : G->Nodes)
|
||||
if (Node->bHasCompilerMessage)
|
||||
{
|
||||
if (!Node) continue;
|
||||
if (Node->bHasCompilerMessage)
|
||||
FString NodeTitle = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
||||
FString NodeMsg = FString::Printf(TEXT("[%s] %s: %s"),
|
||||
*Node->GetGraph()->GetName(), *NodeTitle, *Node->ErrorMsg);
|
||||
if (Node->ErrorType == EMessageSeverity::Error)
|
||||
{
|
||||
FString NodeTitle = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
||||
FString NodeMsg = FString::Printf(TEXT("[%s] %s: %s"),
|
||||
*G->GetName(), *NodeTitle, *Node->ErrorMsg);
|
||||
if (Node->ErrorType == EMessageSeverity::Error)
|
||||
{
|
||||
ErrorsArr.Add(MakeShared<FJsonValueString>(NodeMsg));
|
||||
}
|
||||
else
|
||||
{
|
||||
WarningsArr.Add(MakeShared<FJsonValueString>(NodeMsg));
|
||||
}
|
||||
ErrorsArr.Add(MakeShared<FJsonValueString>(NodeMsg));
|
||||
}
|
||||
else
|
||||
{
|
||||
WarningsArr.Add(MakeShared<FJsonValueString>(NodeMsg));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1445,15 +1402,10 @@ public:
|
||||
UBlueprint* BP = BPAssets.Object();
|
||||
|
||||
FString DecodedGraphName = MCPUtils::UrlDecode(Graph);
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* G : AllGraphs)
|
||||
TArray<UEdGraph*> MatchingGraphs = MCPUtils::AllGraphsNamed(BP, DecodedGraphName);
|
||||
if (MatchingGraphs.Num() > 0)
|
||||
{
|
||||
if (G && G->GetName().Equals(DecodedGraphName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
GraphFilter = G;
|
||||
break;
|
||||
}
|
||||
GraphFilter = MatchingGraphs[0];
|
||||
}
|
||||
if (!GraphFilter)
|
||||
{
|
||||
|
||||
@@ -59,46 +59,28 @@ public:
|
||||
UK2Node_EditablePinBase* EntryNode = nullptr;
|
||||
FString FoundNodeType;
|
||||
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
|
||||
// Strategy 1: Look for a function graph matching the name
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
// Strategy 1: Look for a K2Node_FunctionEntry in a function graph matching the name
|
||||
for (UK2Node_FunctionEntry* FuncEntry : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
|
||||
{
|
||||
if (Graph && Graph->GetName().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
if (FuncEntry->GetGraph()->GetName().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
if (UK2Node_FunctionEntry* FuncEntry = Cast<UK2Node_FunctionEntry>(Node))
|
||||
{
|
||||
EntryNode = FuncEntry;
|
||||
FoundNodeType = TEXT("FunctionEntry");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (EntryNode) break;
|
||||
EntryNode = FuncEntry;
|
||||
FoundNodeType = TEXT("FunctionEntry");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: Search for a K2Node_CustomEvent with matching CustomFunctionName
|
||||
if (!EntryNode)
|
||||
{
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UK2Node_CustomEvent* CustomEvent : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP))
|
||||
{
|
||||
if (!Graph) continue;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
if (CustomEvent->CustomFunctionName.ToString().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
if (UK2Node_CustomEvent* CustomEvent = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
if (CustomEvent->CustomFunctionName.ToString().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
EntryNode = CustomEvent;
|
||||
FoundNodeType = TEXT("CustomEvent");
|
||||
break;
|
||||
}
|
||||
}
|
||||
EntryNode = CustomEvent;
|
||||
FoundNodeType = TEXT("CustomEvent");
|
||||
break;
|
||||
}
|
||||
if (EntryNode) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,22 +88,15 @@ public:
|
||||
{
|
||||
// List available functions/events for debugging
|
||||
TArray<TSharedPtr<FJsonValue>> Available;
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
|
||||
{
|
||||
if (!Graph) continue;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
if (UK2Node_FunctionEntry* FE = Cast<UK2Node_FunctionEntry>(Node))
|
||||
{
|
||||
Available.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("function:%s"), *Graph->GetName())));
|
||||
}
|
||||
else if (UK2Node_CustomEvent* CE = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
Available.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("event:%s"), *CE->CustomFunctionName.ToString())));
|
||||
}
|
||||
}
|
||||
Available.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("function:%s"), *FE->GetGraph()->GetName())));
|
||||
}
|
||||
for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP))
|
||||
{
|
||||
Available.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("event:%s"), *CE->CustomFunctionName.ToString())));
|
||||
}
|
||||
|
||||
MCPUtils::MakeErrorJson(Result, FString::Printf(
|
||||
@@ -261,46 +236,28 @@ public:
|
||||
UK2Node_EditablePinBase* EntryNode = nullptr;
|
||||
FString FoundNodeType;
|
||||
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
|
||||
// Strategy 1: Look for a function graph matching the name
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
// Strategy 1: Look for a K2Node_FunctionEntry in a function graph matching the name
|
||||
for (UK2Node_FunctionEntry* FuncEntry : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
|
||||
{
|
||||
if (Graph && Graph->GetName().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
if (FuncEntry->GetGraph()->GetName().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
if (UK2Node_FunctionEntry* FuncEntry = Cast<UK2Node_FunctionEntry>(Node))
|
||||
{
|
||||
EntryNode = FuncEntry;
|
||||
FoundNodeType = TEXT("FunctionEntry");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (EntryNode) break;
|
||||
EntryNode = FuncEntry;
|
||||
FoundNodeType = TEXT("FunctionEntry");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: Search for a K2Node_CustomEvent with matching CustomFunctionName
|
||||
if (!EntryNode)
|
||||
{
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UK2Node_CustomEvent* CustomEvent : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP))
|
||||
{
|
||||
if (!Graph) continue;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
if (CustomEvent->CustomFunctionName.ToString().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
if (UK2Node_CustomEvent* CustomEvent = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
if (CustomEvent->CustomFunctionName.ToString().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
EntryNode = CustomEvent;
|
||||
FoundNodeType = TEXT("CustomEvent");
|
||||
break;
|
||||
}
|
||||
}
|
||||
EntryNode = CustomEvent;
|
||||
FoundNodeType = TEXT("CustomEvent");
|
||||
break;
|
||||
}
|
||||
if (EntryNode) break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,22 +265,15 @@ public:
|
||||
{
|
||||
// List available functions/events for debugging
|
||||
TArray<TSharedPtr<FJsonValue>> Available;
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
|
||||
{
|
||||
if (!Graph) continue;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
if (UK2Node_FunctionEntry* FE = Cast<UK2Node_FunctionEntry>(Node))
|
||||
{
|
||||
Available.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("function:%s"), *Graph->GetName())));
|
||||
}
|
||||
else if (UK2Node_CustomEvent* CE = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
Available.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("event:%s"), *CE->CustomFunctionName.ToString())));
|
||||
}
|
||||
}
|
||||
Available.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("function:%s"), *FE->GetGraph()->GetName())));
|
||||
}
|
||||
for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP))
|
||||
{
|
||||
Available.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("event:%s"), *CE->CustomFunctionName.ToString())));
|
||||
}
|
||||
|
||||
MCPUtils::MakeErrorJson(Result, FString::Printf(
|
||||
@@ -436,75 +386,44 @@ public:
|
||||
FName FuncFName(*FunctionName);
|
||||
|
||||
// Strategy 1: K2Node_FunctionEntry in function graphs
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
|
||||
{
|
||||
if (!Graph || !Graph->GetName().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UEdGraph* FEGraph = FE->GetGraph();
|
||||
if (!FEGraph->GetName().Equals(FunctionName, ESearchCase::IgnoreCase)) continue;
|
||||
// Skip delegate signature graphs (handled in Strategy 3)
|
||||
if (BP->DelegateSignatureGraphs.Contains(Graph))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (BP->DelegateSignatureGraphs.Contains(FEGraph)) continue;
|
||||
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
if (UK2Node_FunctionEntry* FE = Cast<UK2Node_FunctionEntry>(Node))
|
||||
{
|
||||
EntryNode = FE;
|
||||
NodeType = TEXT("FunctionEntry");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (EntryNode) break;
|
||||
EntryNode = FE;
|
||||
NodeType = TEXT("FunctionEntry");
|
||||
break;
|
||||
}
|
||||
|
||||
// Strategy 2: K2Node_CustomEvent with matching CustomFunctionName
|
||||
if (!EntryNode)
|
||||
{
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP))
|
||||
{
|
||||
if (!Graph) continue;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
if (CE->CustomFunctionName.ToString().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
if (UK2Node_CustomEvent* CE = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
if (CE->CustomFunctionName.ToString().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
EntryNode = CE;
|
||||
NodeType = TEXT("CustomEvent");
|
||||
break;
|
||||
}
|
||||
}
|
||||
EntryNode = CE;
|
||||
NodeType = TEXT("CustomEvent");
|
||||
break;
|
||||
}
|
||||
if (EntryNode) break;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: K2Node_FunctionEntry in DelegateSignatureGraphs
|
||||
if (!EntryNode)
|
||||
{
|
||||
for (UEdGraph* SigGraph : BP->DelegateSignatureGraphs)
|
||||
for (UK2Node_FunctionEntry* FE : MCPUtils::AllNodes<UK2Node_FunctionEntry>(BP))
|
||||
{
|
||||
if (!SigGraph || !SigGraph->GetName().Equals(FunctionName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UEdGraph* FEGraph = FE->GetGraph();
|
||||
if (!FEGraph->GetName().Equals(FunctionName, ESearchCase::IgnoreCase)) continue;
|
||||
if (!BP->DelegateSignatureGraphs.Contains(FEGraph)) continue;
|
||||
|
||||
for (UEdGraphNode* Node : SigGraph->Nodes)
|
||||
{
|
||||
if (UK2Node_FunctionEntry* FE = Cast<UK2Node_FunctionEntry>(Node))
|
||||
{
|
||||
EntryNode = FE;
|
||||
NodeType = TEXT("EventDispatcher");
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (EntryNode) break;
|
||||
EntryNode = FE;
|
||||
NodeType = TEXT("EventDispatcher");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -519,17 +438,10 @@ public:
|
||||
}
|
||||
|
||||
// Custom events
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UK2Node_CustomEvent* CE : MCPUtils::AllNodes<UK2Node_CustomEvent>(BP))
|
||||
{
|
||||
if (!Graph) continue;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
{
|
||||
if (UK2Node_CustomEvent* CE = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
AvailFuncs.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("%s (custom event)"), *CE->CustomFunctionName.ToString())));
|
||||
}
|
||||
}
|
||||
AvailFuncs.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("%s (custom event)"), *CE->CustomFunctionName.ToString())));
|
||||
}
|
||||
|
||||
// Dispatchers
|
||||
|
||||
@@ -194,12 +194,11 @@ public:
|
||||
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
|
||||
UBlueprint* BP = Assets.Object();
|
||||
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
TArray<UEdGraph*> AllGraphs = MCPUtils::AllGraphs(BP);
|
||||
|
||||
for (UEdGraph* GraphObj : AllGraphs)
|
||||
{
|
||||
if (GraphObj && GraphObj->GetName().Equals(DecodedGraphName, ESearchCase::IgnoreCase))
|
||||
if (GraphObj->GetName().Equals(DecodedGraphName, ESearchCase::IgnoreCase))
|
||||
{
|
||||
TSharedPtr<FJsonObject> GraphJson = MCPUtils::SerializeGraph(GraphObj);
|
||||
if (GraphJson.IsValid())
|
||||
@@ -214,10 +213,7 @@ public:
|
||||
TArray<TSharedPtr<FJsonValue>> GraphNames;
|
||||
for (UEdGraph* GraphObj : AllGraphs)
|
||||
{
|
||||
if (GraphObj)
|
||||
{
|
||||
GraphNames.Add(MakeShared<FJsonValueString>(GraphObj->GetName()));
|
||||
}
|
||||
GraphNames.Add(MakeShared<FJsonValueString>(GraphObj->GetName()));
|
||||
}
|
||||
MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Graph '%s' not found"), *DecodedGraphName));
|
||||
Result->SetArrayField(TEXT("availableGraphs"), GraphNames);
|
||||
@@ -255,59 +251,51 @@ public:
|
||||
// Build a combined list of all searchable blueprints (regular + level)
|
||||
auto SearchBlueprint = [&](const FString& AssetName, const FString& AssetPath, UBlueprint* BP, TArray<TSharedPtr<FJsonValue>>& OutResults)
|
||||
{
|
||||
TArray<UEdGraph*> Graphs;
|
||||
BP->GetAllGraphs(Graphs);
|
||||
|
||||
for (UEdGraph* GraphObj : Graphs)
|
||||
for (UEdGraphNode* Node : MCPUtils::AllNodes(BP))
|
||||
{
|
||||
if (!GraphObj || OutResults.Num() >= EffectiveMaxResults) break;
|
||||
if (OutResults.Num() >= EffectiveMaxResults) break;
|
||||
|
||||
for (UEdGraphNode* Node : GraphObj->Nodes)
|
||||
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
||||
|
||||
FString FuncName, EventName, VarName;
|
||||
if (auto* CF = Cast<UK2Node_CallFunction>(Node))
|
||||
{
|
||||
if (!Node || OutResults.Num() >= EffectiveMaxResults) break;
|
||||
FuncName = CF->FunctionReference.GetMemberName().ToString();
|
||||
}
|
||||
else if (auto* Ev = Cast<UK2Node_Event>(Node))
|
||||
{
|
||||
EventName = Ev->EventReference.GetMemberName().ToString();
|
||||
}
|
||||
else if (auto* CE = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
EventName = CE->CustomFunctionName.ToString();
|
||||
}
|
||||
else if (auto* VG = Cast<UK2Node_VariableGet>(Node))
|
||||
{
|
||||
VarName = VG->GetVarName().ToString();
|
||||
}
|
||||
else if (auto* VS = Cast<UK2Node_VariableSet>(Node))
|
||||
{
|
||||
VarName = VS->GetVarName().ToString();
|
||||
}
|
||||
|
||||
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
||||
bool bMatch = Title.Contains(Query, ESearchCase::IgnoreCase) ||
|
||||
(!FuncName.IsEmpty() && FuncName.Contains(Query, ESearchCase::IgnoreCase)) ||
|
||||
(!EventName.IsEmpty() && EventName.Contains(Query, ESearchCase::IgnoreCase)) ||
|
||||
(!VarName.IsEmpty() && VarName.Contains(Query, ESearchCase::IgnoreCase));
|
||||
|
||||
FString FuncName, EventName, VarName;
|
||||
if (auto* CF = Cast<UK2Node_CallFunction>(Node))
|
||||
{
|
||||
FuncName = CF->FunctionReference.GetMemberName().ToString();
|
||||
}
|
||||
else if (auto* Ev = Cast<UK2Node_Event>(Node))
|
||||
{
|
||||
EventName = Ev->EventReference.GetMemberName().ToString();
|
||||
}
|
||||
else if (auto* CE = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
EventName = CE->CustomFunctionName.ToString();
|
||||
}
|
||||
else if (auto* VG = Cast<UK2Node_VariableGet>(Node))
|
||||
{
|
||||
VarName = VG->GetVarName().ToString();
|
||||
}
|
||||
else if (auto* VS = Cast<UK2Node_VariableSet>(Node))
|
||||
{
|
||||
VarName = VS->GetVarName().ToString();
|
||||
}
|
||||
|
||||
bool bMatch = Title.Contains(Query, ESearchCase::IgnoreCase) ||
|
||||
(!FuncName.IsEmpty() && FuncName.Contains(Query, ESearchCase::IgnoreCase)) ||
|
||||
(!EventName.IsEmpty() && EventName.Contains(Query, ESearchCase::IgnoreCase)) ||
|
||||
(!VarName.IsEmpty() && VarName.Contains(Query, ESearchCase::IgnoreCase));
|
||||
|
||||
if (bMatch)
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), AssetName);
|
||||
R->SetStringField(TEXT("blueprintPath"), AssetPath);
|
||||
R->SetStringField(TEXT("graph"), GraphObj->GetName());
|
||||
R->SetStringField(TEXT("nodeTitle"), Title);
|
||||
R->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName());
|
||||
if (!FuncName.IsEmpty()) R->SetStringField(TEXT("functionName"), FuncName);
|
||||
if (!EventName.IsEmpty()) R->SetStringField(TEXT("eventName"), EventName);
|
||||
if (!VarName.IsEmpty()) R->SetStringField(TEXT("variableName"), VarName);
|
||||
OutResults.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
if (bMatch)
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), AssetName);
|
||||
R->SetStringField(TEXT("blueprintPath"), AssetPath);
|
||||
R->SetStringField(TEXT("graph"), Node->GetGraph()->GetName());
|
||||
R->SetStringField(TEXT("nodeTitle"), Title);
|
||||
R->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName());
|
||||
if (!FuncName.IsEmpty()) R->SetStringField(TEXT("functionName"), FuncName);
|
||||
if (!EventName.IsEmpty()) R->SetStringField(TEXT("eventName"), EventName);
|
||||
if (!VarName.IsEmpty()) R->SetStringField(TEXT("variableName"), VarName);
|
||||
OutResults.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -549,137 +537,129 @@ public:
|
||||
}
|
||||
|
||||
// Check graphs for function/event params, struct nodes, and pin connections
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
|
||||
for (UEdGraph* GraphObj : AllGraphs)
|
||||
for (UEdGraphNode* Node : MCPUtils::AllNodes(BP))
|
||||
{
|
||||
if (!GraphObj || Results.Num() >= EffectiveMaxResults) break;
|
||||
if (Results.Num() >= EffectiveMaxResults) break;
|
||||
|
||||
for (UEdGraphNode* Node : GraphObj->Nodes)
|
||||
// Check FunctionEntry/CustomEvent parameters
|
||||
if (auto* FuncEntry = Cast<UK2Node_FunctionEntry>(Node))
|
||||
{
|
||||
if (!Node || Results.Num() >= EffectiveMaxResults) break;
|
||||
|
||||
// Check FunctionEntry/CustomEvent parameters
|
||||
if (auto* FuncEntry = Cast<UK2Node_FunctionEntry>(Node))
|
||||
for (const TSharedPtr<FUserPinInfo>& PinInfo : FuncEntry->UserDefinedPins)
|
||||
{
|
||||
for (const TSharedPtr<FUserPinInfo>& PinInfo : FuncEntry->UserDefinedPins)
|
||||
{
|
||||
if (!PinInfo.IsValid()) continue;
|
||||
FString ParamSubtype;
|
||||
if (PinInfo->PinType.PinSubCategoryObject.IsValid())
|
||||
ParamSubtype = PinInfo->PinType.PinSubCategoryObject->GetName();
|
||||
if (!PinInfo.IsValid()) continue;
|
||||
FString ParamSubtype;
|
||||
if (PinInfo->PinType.PinSubCategoryObject.IsValid())
|
||||
ParamSubtype = PinInfo->PinType.PinSubCategoryObject->GetName();
|
||||
|
||||
if (MatchesType(ParamSubtype) || MatchesType(PinInfo->PinType.PinCategory.ToString()))
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), BPName);
|
||||
R->SetStringField(TEXT("blueprintPath"), BPPath);
|
||||
R->SetStringField(TEXT("usage"), TEXT("functionParameter"));
|
||||
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"),
|
||||
*GraphObj->GetName(), *PinInfo->PinName.ToString()));
|
||||
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
R->SetStringField(TEXT("currentType"), PinInfo->PinType.PinCategory.ToString());
|
||||
if (!ParamSubtype.IsEmpty())
|
||||
R->SetStringField(TEXT("currentSubtype"), ParamSubtype);
|
||||
if (bIsLevel)
|
||||
R->SetBoolField(TEXT("isLevelBlueprint"), true);
|
||||
Results.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto* CustomEvent = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
for (const TSharedPtr<FUserPinInfo>& PinInfo : CustomEvent->UserDefinedPins)
|
||||
{
|
||||
if (!PinInfo.IsValid()) continue;
|
||||
FString ParamSubtype;
|
||||
if (PinInfo->PinType.PinSubCategoryObject.IsValid())
|
||||
ParamSubtype = PinInfo->PinType.PinSubCategoryObject->GetName();
|
||||
|
||||
if (MatchesType(ParamSubtype) || MatchesType(PinInfo->PinType.PinCategory.ToString()))
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), BPName);
|
||||
R->SetStringField(TEXT("blueprintPath"), BPPath);
|
||||
R->SetStringField(TEXT("usage"), TEXT("eventParameter"));
|
||||
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"),
|
||||
*CustomEvent->CustomFunctionName.ToString(), *PinInfo->PinName.ToString()));
|
||||
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
R->SetStringField(TEXT("currentType"), PinInfo->PinType.PinCategory.ToString());
|
||||
if (!ParamSubtype.IsEmpty())
|
||||
R->SetStringField(TEXT("currentSubtype"), ParamSubtype);
|
||||
if (bIsLevel)
|
||||
R->SetBoolField(TEXT("isLevelBlueprint"), true);
|
||||
Results.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check Break/Make struct nodes
|
||||
else if (auto* BreakNode = Cast<UK2Node_BreakStruct>(Node))
|
||||
{
|
||||
if (BreakNode->StructType && MatchesType(BreakNode->StructType->GetName()))
|
||||
if (MatchesType(ParamSubtype) || MatchesType(PinInfo->PinType.PinCategory.ToString()))
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), BPName);
|
||||
R->SetStringField(TEXT("blueprintPath"), BPPath);
|
||||
R->SetStringField(TEXT("usage"), TEXT("breakStruct"));
|
||||
R->SetStringField(TEXT("location"), GraphObj->GetName());
|
||||
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
R->SetStringField(TEXT("structType"), BreakNode->StructType->GetName());
|
||||
if (bIsLevel)
|
||||
R->SetBoolField(TEXT("isLevelBlueprint"), true);
|
||||
Results.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
else if (auto* MakeNode = Cast<UK2Node_MakeStruct>(Node))
|
||||
{
|
||||
if (MakeNode->StructType && MatchesType(MakeNode->StructType->GetName()))
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), BPName);
|
||||
R->SetStringField(TEXT("blueprintPath"), BPPath);
|
||||
R->SetStringField(TEXT("usage"), TEXT("makeStruct"));
|
||||
R->SetStringField(TEXT("location"), GraphObj->GetName());
|
||||
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
R->SetStringField(TEXT("structType"), MakeNode->StructType->GetName());
|
||||
if (bIsLevel)
|
||||
R->SetBoolField(TEXT("isLevelBlueprint"), true);
|
||||
Results.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
|
||||
// Check pin connections carrying the type
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (!Pin || Pin->bHidden || Results.Num() >= EffectiveMaxResults) continue;
|
||||
|
||||
FString PinSubtype;
|
||||
if (Pin->PinType.PinSubCategoryObject.IsValid())
|
||||
PinSubtype = Pin->PinType.PinSubCategoryObject->GetName();
|
||||
|
||||
if (Pin->LinkedTo.Num() > 0 &&
|
||||
(MatchesType(PinSubtype) || MatchesType(Pin->PinType.PinCategory.ToString())))
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), BPName);
|
||||
R->SetStringField(TEXT("blueprintPath"), BPPath);
|
||||
R->SetStringField(TEXT("usage"), TEXT("pinConnection"));
|
||||
R->SetStringField(TEXT("usage"), TEXT("functionParameter"));
|
||||
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"),
|
||||
*Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(),
|
||||
*Pin->PinName.ToString()));
|
||||
*Node->GetGraph()->GetName(), *PinInfo->PinName.ToString()));
|
||||
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
R->SetStringField(TEXT("graph"), GraphObj->GetName());
|
||||
R->SetStringField(TEXT("pinType"), Pin->PinType.PinCategory.ToString());
|
||||
if (!PinSubtype.IsEmpty())
|
||||
R->SetStringField(TEXT("pinSubtype"), PinSubtype);
|
||||
R->SetNumberField(TEXT("connectionCount"), Pin->LinkedTo.Num());
|
||||
R->SetStringField(TEXT("currentType"), PinInfo->PinType.PinCategory.ToString());
|
||||
if (!ParamSubtype.IsEmpty())
|
||||
R->SetStringField(TEXT("currentSubtype"), ParamSubtype);
|
||||
if (bIsLevel)
|
||||
R->SetBoolField(TEXT("isLevelBlueprint"), true);
|
||||
Results.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (auto* CustomEvent = Cast<UK2Node_CustomEvent>(Node))
|
||||
{
|
||||
for (const TSharedPtr<FUserPinInfo>& PinInfo : CustomEvent->UserDefinedPins)
|
||||
{
|
||||
if (!PinInfo.IsValid()) continue;
|
||||
FString ParamSubtype;
|
||||
if (PinInfo->PinType.PinSubCategoryObject.IsValid())
|
||||
ParamSubtype = PinInfo->PinType.PinSubCategoryObject->GetName();
|
||||
|
||||
if (MatchesType(ParamSubtype) || MatchesType(PinInfo->PinType.PinCategory.ToString()))
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), BPName);
|
||||
R->SetStringField(TEXT("blueprintPath"), BPPath);
|
||||
R->SetStringField(TEXT("usage"), TEXT("eventParameter"));
|
||||
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"),
|
||||
*CustomEvent->CustomFunctionName.ToString(), *PinInfo->PinName.ToString()));
|
||||
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
R->SetStringField(TEXT("currentType"), PinInfo->PinType.PinCategory.ToString());
|
||||
if (!ParamSubtype.IsEmpty())
|
||||
R->SetStringField(TEXT("currentSubtype"), ParamSubtype);
|
||||
if (bIsLevel)
|
||||
R->SetBoolField(TEXT("isLevelBlueprint"), true);
|
||||
Results.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check Break/Make struct nodes
|
||||
else if (auto* BreakNode = Cast<UK2Node_BreakStruct>(Node))
|
||||
{
|
||||
if (BreakNode->StructType && MatchesType(BreakNode->StructType->GetName()))
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), BPName);
|
||||
R->SetStringField(TEXT("blueprintPath"), BPPath);
|
||||
R->SetStringField(TEXT("usage"), TEXT("breakStruct"));
|
||||
R->SetStringField(TEXT("location"), Node->GetGraph()->GetName());
|
||||
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
R->SetStringField(TEXT("structType"), BreakNode->StructType->GetName());
|
||||
if (bIsLevel)
|
||||
R->SetBoolField(TEXT("isLevelBlueprint"), true);
|
||||
Results.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
else if (auto* MakeNode = Cast<UK2Node_MakeStruct>(Node))
|
||||
{
|
||||
if (MakeNode->StructType && MatchesType(MakeNode->StructType->GetName()))
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), BPName);
|
||||
R->SetStringField(TEXT("blueprintPath"), BPPath);
|
||||
R->SetStringField(TEXT("usage"), TEXT("makeStruct"));
|
||||
R->SetStringField(TEXT("location"), Node->GetGraph()->GetName());
|
||||
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
R->SetStringField(TEXT("structType"), MakeNode->StructType->GetName());
|
||||
if (bIsLevel)
|
||||
R->SetBoolField(TEXT("isLevelBlueprint"), true);
|
||||
Results.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
|
||||
// Check pin connections carrying the type
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (!Pin || Pin->bHidden || Results.Num() >= EffectiveMaxResults) continue;
|
||||
|
||||
FString PinSubtype;
|
||||
if (Pin->PinType.PinSubCategoryObject.IsValid())
|
||||
PinSubtype = Pin->PinType.PinSubCategoryObject->GetName();
|
||||
|
||||
if ((Pin->LinkedTo.Num() > 0) &&
|
||||
(MatchesType(PinSubtype) || MatchesType(Pin->PinType.PinCategory.ToString())))
|
||||
{
|
||||
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
||||
R->SetStringField(TEXT("blueprint"), BPName);
|
||||
R->SetStringField(TEXT("blueprintPath"), BPPath);
|
||||
R->SetStringField(TEXT("usage"), TEXT("pinConnection"));
|
||||
R->SetStringField(TEXT("location"), FString::Printf(TEXT("%s.%s"),
|
||||
*Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString(),
|
||||
*Pin->PinName.ToString()));
|
||||
R->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
R->SetStringField(TEXT("graph"), Node->GetGraph()->GetName());
|
||||
R->SetStringField(TEXT("pinType"), Pin->PinType.PinCategory.ToString());
|
||||
if (!PinSubtype.IsEmpty())
|
||||
R->SetStringField(TEXT("pinSubtype"), PinSubtype);
|
||||
R->SetNumberField(TEXT("connectionCount"), Pin->LinkedTo.Num());
|
||||
if (bIsLevel)
|
||||
R->SetBoolField(TEXT("isLevelBlueprint"), true);
|
||||
Results.Add(MakeShared<FJsonValueObject>(R));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -61,34 +61,26 @@ public:
|
||||
TArray<TSharedPtr<FJsonValue>> ErrorsArr;
|
||||
TArray<TSharedPtr<FJsonValue>> WarningsArr;
|
||||
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UEdGraphNode* Node : MCPUtils::AllNodes(BP))
|
||||
{
|
||||
if (!Graph) continue;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
if (Node->bHasCompilerMessage)
|
||||
{
|
||||
if (!Node) continue;
|
||||
if (Node->bHasCompilerMessage)
|
||||
{
|
||||
TSharedRef<FJsonObject> Msg = MakeShared<FJsonObject>();
|
||||
Msg->SetStringField(TEXT("graph"), Graph->GetName());
|
||||
Msg->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
Msg->SetStringField(TEXT("nodeTitle"), Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString());
|
||||
Msg->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName());
|
||||
Msg->SetStringField(TEXT("message"), Node->ErrorMsg);
|
||||
TSharedRef<FJsonObject> Msg = MakeShared<FJsonObject>();
|
||||
Msg->SetStringField(TEXT("graph"), Node->GetGraph()->GetName());
|
||||
Msg->SetStringField(TEXT("nodeId"), Node->NodeGuid.ToString());
|
||||
Msg->SetStringField(TEXT("nodeTitle"), Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString());
|
||||
Msg->SetStringField(TEXT("nodeClass"), Node->GetClass()->GetName());
|
||||
Msg->SetStringField(TEXT("message"), Node->ErrorMsg);
|
||||
|
||||
if (Node->ErrorType == EMessageSeverity::Error)
|
||||
{
|
||||
Msg->SetStringField(TEXT("severity"), TEXT("error"));
|
||||
ErrorsArr.Add(MakeShared<FJsonValueObject>(Msg));
|
||||
}
|
||||
else
|
||||
{
|
||||
Msg->SetStringField(TEXT("severity"), TEXT("warning"));
|
||||
WarningsArr.Add(MakeShared<FJsonValueObject>(Msg));
|
||||
}
|
||||
if (Node->ErrorType == EMessageSeverity::Error)
|
||||
{
|
||||
Msg->SetStringField(TEXT("severity"), TEXT("error"));
|
||||
ErrorsArr.Add(MakeShared<FJsonValueObject>(Msg));
|
||||
}
|
||||
else
|
||||
{
|
||||
Msg->SetStringField(TEXT("severity"), TEXT("warning"));
|
||||
WarningsArr.Add(MakeShared<FJsonValueObject>(Msg));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -121,7 +113,7 @@ public:
|
||||
default: StatusStr = FString::Printf(TEXT("Status_%d"), (int32)BP->Status); break;
|
||||
}
|
||||
|
||||
bool bIsValid = (BP->Status == BS_UpToDate) && ErrorsArr.Num() == 0;
|
||||
bool bIsValid = (BP->Status == BS_UpToDate) && (ErrorsArr.Num() == 0);
|
||||
|
||||
TSharedRef<FJsonObject> Result = MakeShared<FJsonObject>();
|
||||
Result->SetStringField(TEXT("blueprint"), BlueprintName);
|
||||
|
||||
@@ -107,60 +107,45 @@ public:
|
||||
|
||||
// Analyze affected nodes (get/set nodes for this variable)
|
||||
TArray<TSharedPtr<FJsonValue>> AffectedNodes;
|
||||
TArray<UEdGraph*> AllGraphs;
|
||||
BP->GetAllGraphs(AllGraphs);
|
||||
for (UEdGraph* Graph : AllGraphs)
|
||||
for (UK2Node_VariableGet* VG : MCPUtils::AllNodes<UK2Node_VariableGet>(BP))
|
||||
{
|
||||
if (!Graph) continue;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
if (VG->GetVarName().ToString() != Variable) continue;
|
||||
TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>();
|
||||
AffNode->SetStringField(TEXT("nodeId"), VG->NodeGuid.ToString());
|
||||
AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableGet"));
|
||||
AffNode->SetStringField(TEXT("graph"), VG->GetGraph()->GetName());
|
||||
TArray<TSharedPtr<FJsonValue>> AffPins;
|
||||
for (UEdGraphPin* Pin : VG->Pins)
|
||||
{
|
||||
if (!Node) continue;
|
||||
if (auto* VG = Cast<UK2Node_VariableGet>(Node))
|
||||
if (Pin && (Pin->LinkedTo.Num() > 0) && (Pin->Direction == EGPD_Output))
|
||||
{
|
||||
if (VG->GetVarName().ToString() == Variable)
|
||||
{
|
||||
TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>();
|
||||
AffNode->SetStringField(TEXT("nodeId"), VG->NodeGuid.ToString());
|
||||
AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableGet"));
|
||||
AffNode->SetStringField(TEXT("graph"), Graph->GetName());
|
||||
// Check which pins would be affected
|
||||
TArray<TSharedPtr<FJsonValue>> AffPins;
|
||||
for (UEdGraphPin* Pin : VG->Pins)
|
||||
{
|
||||
if (Pin && Pin->LinkedTo.Num() > 0 && Pin->Direction == EGPD_Output)
|
||||
{
|
||||
AffPins.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("%s (connected to %d pin(s))"),
|
||||
*Pin->PinName.ToString(), Pin->LinkedTo.Num())));
|
||||
}
|
||||
}
|
||||
AffNode->SetArrayField(TEXT("affectedPins"), AffPins);
|
||||
AffectedNodes.Add(MakeShared<FJsonValueObject>(AffNode));
|
||||
}
|
||||
}
|
||||
else if (auto* VS = Cast<UK2Node_VariableSet>(Node))
|
||||
{
|
||||
if (VS->GetVarName().ToString() == Variable)
|
||||
{
|
||||
TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>();
|
||||
AffNode->SetStringField(TEXT("nodeId"), VS->NodeGuid.ToString());
|
||||
AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableSet"));
|
||||
AffNode->SetStringField(TEXT("graph"), Graph->GetName());
|
||||
TArray<TSharedPtr<FJsonValue>> AffPins;
|
||||
for (UEdGraphPin* Pin : VS->Pins)
|
||||
{
|
||||
if (Pin && Pin->LinkedTo.Num() > 0)
|
||||
{
|
||||
AffPins.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("%s (connected to %d pin(s))"),
|
||||
*Pin->PinName.ToString(), Pin->LinkedTo.Num())));
|
||||
}
|
||||
}
|
||||
AffNode->SetArrayField(TEXT("affectedPins"), AffPins);
|
||||
AffectedNodes.Add(MakeShared<FJsonValueObject>(AffNode));
|
||||
}
|
||||
AffPins.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("%s (connected to %d pin(s))"),
|
||||
*Pin->PinName.ToString(), Pin->LinkedTo.Num())));
|
||||
}
|
||||
}
|
||||
AffNode->SetArrayField(TEXT("affectedPins"), AffPins);
|
||||
AffectedNodes.Add(MakeShared<FJsonValueObject>(AffNode));
|
||||
}
|
||||
for (UK2Node_VariableSet* VS : MCPUtils::AllNodes<UK2Node_VariableSet>(BP))
|
||||
{
|
||||
if (VS->GetVarName().ToString() != Variable) continue;
|
||||
TSharedRef<FJsonObject> AffNode = MakeShared<FJsonObject>();
|
||||
AffNode->SetStringField(TEXT("nodeId"), VS->NodeGuid.ToString());
|
||||
AffNode->SetStringField(TEXT("nodeType"), TEXT("VariableSet"));
|
||||
AffNode->SetStringField(TEXT("graph"), VS->GetGraph()->GetName());
|
||||
TArray<TSharedPtr<FJsonValue>> AffPins;
|
||||
for (UEdGraphPin* Pin : VS->Pins)
|
||||
{
|
||||
if (Pin && Pin->LinkedTo.Num() > 0)
|
||||
{
|
||||
AffPins.Add(MakeShared<FJsonValueString>(
|
||||
FString::Printf(TEXT("%s (connected to %d pin(s))"),
|
||||
*Pin->PinName.ToString(), Pin->LinkedTo.Num())));
|
||||
}
|
||||
}
|
||||
AffNode->SetArrayField(TEXT("affectedPins"), AffPins);
|
||||
AffectedNodes.Add(MakeShared<FJsonValueObject>(AffNode));
|
||||
}
|
||||
|
||||
if (DryRun)
|
||||
|
||||
@@ -251,7 +251,6 @@ int32 TryAddMaterialExpressionSEH(
|
||||
|
||||
void FMCPServer::DispatchToolCall(const FString& ToolName, const FJsonObject* Params, FJsonObject* Result)
|
||||
{
|
||||
UMCPAssetFinder::Refresh();
|
||||
if (UClass** HandlerClass = MCPHandlerRegistry.Find(ToolName))
|
||||
{
|
||||
const bool bIsMutation = MutationEndpoints.Contains(ToolName);
|
||||
|
||||
@@ -189,6 +189,38 @@ FString MCPUtils::UrlDecode(const FString& EncodedString)
|
||||
// Blueprint helpers
|
||||
// ============================================================
|
||||
|
||||
TArray<UEdGraph*> MCPUtils::AllGraphs(UBlueprint* BP)
|
||||
{
|
||||
TArray<UEdGraph*> Graphs;
|
||||
BP->GetAllGraphs(Graphs);
|
||||
return Graphs;
|
||||
}
|
||||
|
||||
TArray<UEdGraph*> MCPUtils::AllGraphsNamed(UBlueprint* BP, const FString& Name)
|
||||
{
|
||||
TArray<UEdGraph*> Result;
|
||||
for (UEdGraph* Graph : AllGraphs(BP))
|
||||
if (Graph->GetName().Equals(Name, ESearchCase::IgnoreCase))
|
||||
Result.Add(Graph);
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<UEdGraphNode*> MCPUtils::AllNodes(UBlueprint* BP)
|
||||
{
|
||||
TArray<UEdGraphNode*> Nodes;
|
||||
for (UEdGraph* Graph : AllGraphs(BP))
|
||||
Nodes.Append(Graph->Nodes);
|
||||
return Nodes;
|
||||
}
|
||||
|
||||
TArray<TSharedPtr<FJsonValue>> MCPUtils::AllGraphNamesJson(UBlueprint* BP)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Result;
|
||||
for (UEdGraph* Graph : AllGraphs(BP))
|
||||
Result.Add(MakeShared<FJsonValueString>(Graph->GetName()));
|
||||
return Result;
|
||||
}
|
||||
|
||||
UEdGraphNode* MCPUtils::FindNodeByGuid(
|
||||
UBlueprint* BP, const FString& GuidString, UEdGraph** OutGraph)
|
||||
{
|
||||
|
||||
@@ -1,112 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Subsystems/EngineSubsystem.h"
|
||||
#include "AssetRegistry/AssetData.h"
|
||||
#include "StructUtils/UserDefinedStruct.h"
|
||||
#include "Engine/UserDefinedEnum.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/LevelScriptBlueprint.h"
|
||||
#include "Engine/World.h"
|
||||
#include "MCPAssetFinder.generated.h"
|
||||
class UMaterial;
|
||||
class UMaterialInstanceConstant;
|
||||
class UMaterialFunction;
|
||||
class UAnimationStateMachineGraph;
|
||||
class IAssetRegistry;
|
||||
|
||||
struct FARFilter;
|
||||
|
||||
/**
|
||||
* Engine subsystem that caches asset registry data for the BlueprintMCP server.
|
||||
* All public API is static — callers never need to fetch the subsystem directly.
|
||||
* Asset lists are auto-refreshed when the asset registry signals changes.
|
||||
*/
|
||||
UCLASS()
|
||||
class UMCPAssetFinder : public UEngineSubsystem
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
|
||||
virtual void Deinitialize() override;
|
||||
|
||||
static FName BlueprintsAndMaps;
|
||||
|
||||
// Call once before processing a request. Rebuilds caches if the asset registry has changed.
|
||||
// After this call, all query methods read from a stable snapshot until the next Refresh().
|
||||
static void Refresh();
|
||||
|
||||
// --- Static API: asset lists ---
|
||||
static const TArray<FAssetData>& GetAssets(FName Class);
|
||||
static const TArray<FAssetData>& GetAssets(UClass *Class);
|
||||
|
||||
// --- Static API: find/load helpers ---
|
||||
static FAssetData* FindAsset(FName Class, const FString& NameOrPath, FString* OutError = nullptr);
|
||||
static FAssetData* FindAsset(UClass *Class, const FString& NameOrPath, FString* OutError = nullptr);
|
||||
|
||||
// --- Static API: find/load helpers ---
|
||||
static TArray<FAssetData*> SearchAssets(FName Class, const FString& NameOrPath, FString* OutError = nullptr);
|
||||
static TArray<FAssetData*> SearchAssets(UClass *Class, const FString& NameOrPath, FString* OutError = nullptr);
|
||||
|
||||
// Load an asset from an FAssetData. Returns nullptr and reports error on failure.
|
||||
template<typename T>
|
||||
static T* LoadAsset(FAssetData& Asset, MCPErrorCallback Error)
|
||||
{
|
||||
T* Result = Cast<T>(Asset.GetAsset());
|
||||
if (!Result)
|
||||
Error.SetError(FString::Printf(TEXT("Asset '%s' found but could not be loaded as %s."),
|
||||
*Asset.AssetName.ToString(), *T::StaticClass()->GetName()));
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Load an asset by name or path. Returns nullptr and reports error on failure.
|
||||
template<typename T>
|
||||
static T* LoadAsset(const FString& NameOrPath, MCPErrorCallback Error)
|
||||
{
|
||||
FString FindError;
|
||||
FAssetData* Asset = FindAsset(T::StaticClass(), NameOrPath, &FindError);
|
||||
if (!Asset)
|
||||
{
|
||||
if (FindError.IsEmpty())
|
||||
FindError = FString::Printf(TEXT("'%s' not found."), *NameOrPath);
|
||||
Error.SetError(FindError);
|
||||
return nullptr;
|
||||
}
|
||||
return LoadAsset<T>(*Asset, Error);
|
||||
}
|
||||
|
||||
static FAssetData* FindAnyAsset(const FString& NameOrPath, FString* OutError = nullptr);
|
||||
|
||||
// Load a blueprint or level blueprint from an asset (handles UWorld → level blueprint extraction).
|
||||
static UBlueprint* LoadBlueprintOrLevelBlueprint(FAssetData& Asset, MCPErrorCallback Error);
|
||||
static UBlueprint* LoadBlueprintOrLevelBlueprint(const FString& NameOrPath, MCPErrorCallback Error);
|
||||
|
||||
// Load an anim blueprint and find a state machine graph within it.
|
||||
// Callers can recover the AnimBP via SMGraph->GetTypedOuter<UAnimBlueprint>().
|
||||
static UAnimationStateMachineGraph* LoadAnimStateMachineGraph(
|
||||
const FString& BlueprintName, const FString& GraphName, MCPErrorCallback Error);
|
||||
|
||||
private:
|
||||
// Fetch assets from the Unreal asset registry and store them locally.
|
||||
void CacheAssets(IAssetRegistry &Registry, UClass *Class, bool IncludeSubclasses);
|
||||
|
||||
// Returns the subsystem instance, or nullptr if the engine is not initialized.
|
||||
static UMCPAssetFinder* Get();
|
||||
|
||||
// All cached asset lists, keyed by UClass::GetName() (e.g. "Blueprint", "World", "Material").
|
||||
// The special key "BlueprintsAndMaps" combines Blueprint and World assets.
|
||||
TMap<FName, TArray<FAssetData>> AssetCache;
|
||||
|
||||
// Change detection — set true by asset registry delegates
|
||||
bool bDirty = true;
|
||||
void OnAssetEvent(const FAssetData&) { bDirty = true; }
|
||||
void OnAssetRenamed(const FAssetData&, const FString&) { bDirty = true; }
|
||||
|
||||
// Empty array returned when subsystem is unavailable
|
||||
static const TArray<FAssetData> EmptyAssetArray;
|
||||
};
|
||||
|
||||
// ============================================================
|
||||
// MCPAssetsBase — non-template base for MCPAssets<T>
|
||||
// ============================================================
|
||||
|
||||
@@ -98,6 +98,27 @@ public:
|
||||
static FString UrlDecode(const FString& EncodedString);
|
||||
|
||||
// ----- Blueprint helpers -----
|
||||
static TArray<UEdGraph*> AllGraphs(UBlueprint* BP);
|
||||
static TArray<UEdGraph*> AllGraphsNamed(UBlueprint* BP, const FString& Name);
|
||||
static TArray<UEdGraphNode*> AllNodes(UBlueprint* BP);
|
||||
template<class T> static TArray<T*> AllNodes(UBlueprint* BP)
|
||||
{
|
||||
TArray<T*> Result;
|
||||
for (UEdGraph* Graph : AllGraphs(BP))
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
if (T* Typed = Cast<T>(Node))
|
||||
Result.Add(Typed);
|
||||
return Result;
|
||||
}
|
||||
template<class T> static TArray<T*> AllNodes(UEdGraph* Graph)
|
||||
{
|
||||
TArray<T*> Result;
|
||||
for (UEdGraphNode* Node : Graph->Nodes)
|
||||
if (T* Typed = Cast<T>(Node))
|
||||
Result.Add(Typed);
|
||||
return Result;
|
||||
}
|
||||
static TArray<TSharedPtr<FJsonValue>> AllGraphNamesJson(UBlueprint* BP);
|
||||
static UEdGraphNode* FindNodeByGuid(UBlueprint* BP, const FString& GuidString, UEdGraph** OutGraph = nullptr);
|
||||
static bool SaveBlueprintPackage(UBlueprint* BP);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user