MCPFetcher, and new code for FormatName
This commit is contained in:
239
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp
Normal file
239
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "MCPFetcher.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "UObject/PropertyAccessUtil.h"
|
||||
#include "Engine/SimpleConstructionScript.h"
|
||||
#include "Engine/SCS_Node.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/LevelScriptBlueprint.h"
|
||||
|
||||
MCPFetcher& MCPFetcher::SetError(const FString& Msg)
|
||||
{
|
||||
bError = true;
|
||||
ErrorCB.SetError(Msg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expected)
|
||||
{
|
||||
bError = true;
|
||||
if (ResultPin)
|
||||
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is a pin, expected %s"), Walker, Expected));
|
||||
else if (Obj)
|
||||
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is %s, expected %s"), Walker, *Obj->GetClass()->GetName(), Expected));
|
||||
else
|
||||
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is null, expected %s"), Walker, Expected));
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Walk(const FString& Path)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
// Split on commas
|
||||
TArray<FString> Segments;
|
||||
Path.ParseIntoArray(Segments, TEXT(","));
|
||||
if (Segments.Num() == 0)
|
||||
{
|
||||
SetError(TEXT("Empty path"));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// If no object yet, first segment is an asset path
|
||||
int32 Start = 0;
|
||||
if (!Obj && !ResultPin)
|
||||
{
|
||||
LoadUAsset(Segments[0]);
|
||||
if (bError) return *this;
|
||||
Start = 1;
|
||||
}
|
||||
|
||||
// Walk each subsequent segment
|
||||
for (int32 i = Start; i < Segments.Num(); i++)
|
||||
{
|
||||
FString Key, Value;
|
||||
Segments[i].Split(TEXT(":"), &Key, &Value);
|
||||
|
||||
if (StrEq(Key, TEXT("graph"))) Graph(Value);
|
||||
else if (StrEq(Key, TEXT("node"))) Node(Value);
|
||||
else if (StrEq(Key, TEXT("pin"))) Pin(Value);
|
||||
else if (StrEq(Key, TEXT("component"))) Component(Value);
|
||||
else if (StrEq(Key, TEXT("property"))) Property(Value);
|
||||
else if (StrEq(Key, TEXT("levelblueprint"))) LevelBlueprint();
|
||||
else
|
||||
{
|
||||
SetError(FString::Printf(TEXT("Unknown walker '%s'"), *Key));
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (bError) return *this;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void MCPFetcher::LoadUAsset(const FString& PackagePath)
|
||||
{
|
||||
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
|
||||
if (!Obj)
|
||||
SetError(FString::Printf(TEXT("Could not load asset '%s'"), *PackagePath));
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Graph(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
UBlueprint* BP = ::Cast<UBlueprint>(Obj);
|
||||
if (!BP)
|
||||
return TypeMismatch(TEXT("graph"), TEXT("Blueprint"));
|
||||
|
||||
TArray<UEdGraph*> Matches = MCPUtils::AllGraphsNamed(BP, Value);
|
||||
if (Matches.Num() == 0)
|
||||
return SetError(FString::Printf(TEXT("Graph '%s' not found in %s"), *Value, *BP->GetName()));
|
||||
if (Matches.Num() > 1)
|
||||
return SetError(FString::Printf(TEXT("Ambiguous graph '%s' in %s — %d matches"), *Value, *BP->GetName(), Matches.Num()));
|
||||
|
||||
SetObj(Matches[0]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Node(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
// If current object is a graph, search that graph
|
||||
if (UEdGraph* G = ::Cast<UEdGraph>(Obj))
|
||||
{
|
||||
UEdGraphNode* Found = nullptr;
|
||||
for (UEdGraphNode* N : G->Nodes)
|
||||
{
|
||||
if (!N || !MCPUtils::Identifies(Value, N))
|
||||
continue;
|
||||
if (Found)
|
||||
return SetError(FString::Printf(TEXT("Ambiguous node '%s' in graph %s"), *Value, *G->GetName()));
|
||||
Found = N;
|
||||
}
|
||||
if (!Found)
|
||||
return SetError(FString::Printf(TEXT("Node '%s' not found in graph %s"), *Value, *G->GetName()));
|
||||
SetObj(Found);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// If current object is a blueprint, search all graphs
|
||||
if (UBlueprint* BP = ::Cast<UBlueprint>(Obj))
|
||||
{
|
||||
UEdGraphNode* Found = nullptr;
|
||||
for (UEdGraph* G : MCPUtils::AllGraphs(BP))
|
||||
{
|
||||
for (UEdGraphNode* N : G->Nodes)
|
||||
{
|
||||
if (!N || !MCPUtils::Identifies(Value, N))
|
||||
continue;
|
||||
if (Found)
|
||||
return SetError(FString::Printf(TEXT("Ambiguous node '%s' in %s"), *Value, *BP->GetName()));
|
||||
Found = N;
|
||||
}
|
||||
}
|
||||
if (!Found)
|
||||
return SetError(FString::Printf(TEXT("Node '%s' not found in %s"), *Value, *BP->GetName()));
|
||||
SetObj(Found);
|
||||
return *this;
|
||||
}
|
||||
|
||||
return TypeMismatch(TEXT("node"), TEXT("graph or Blueprint"));
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Pin(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
UEdGraphNode* N = ::Cast<UEdGraphNode>(Obj);
|
||||
if (!N)
|
||||
return TypeMismatch(TEXT("pin"), TEXT("node"));
|
||||
UEdGraphPin* Found = nullptr;
|
||||
for (UEdGraphPin *P : N->Pins)
|
||||
{
|
||||
if (!MCPUtils::Identifies(Value, P))
|
||||
continue;
|
||||
if (Found)
|
||||
return SetError(FString::Printf(TEXT("Ambiguous pin '%s' on node %s"),
|
||||
*Value, *MCPUtils::FormatName(N)));
|
||||
Found = P;
|
||||
}
|
||||
if (!Found)
|
||||
return SetError(FString::Printf(TEXT("Pin '%s' not found on node %s"),
|
||||
*Value, *MCPUtils::FormatName(N)));
|
||||
|
||||
SetPin(Found);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Component(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
UBlueprint* BP = ::Cast<UBlueprint>(Obj);
|
||||
if (!BP)
|
||||
return TypeMismatch(TEXT("component"), TEXT("Blueprint"));
|
||||
|
||||
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
|
||||
if (!SCS)
|
||||
return SetError(FString::Printf(TEXT("Blueprint %s has no SimpleConstructionScript (not an Actor Blueprint)"), *BP->GetName()));
|
||||
|
||||
FName SearchName(*Value);
|
||||
for (USCS_Node* SCSNode : SCS->GetAllNodes())
|
||||
{
|
||||
if (SCSNode && SCSNode->GetVariableName() == SearchName)
|
||||
{
|
||||
SetObj(SCSNode->ComponentTemplate);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
return SetError(FString::Printf(TEXT("Component '%s' not found in %s"), *Value, *BP->GetName()));
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Property(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
if (!Obj)
|
||||
return TypeMismatch(TEXT("property"), TEXT("UObject"));
|
||||
|
||||
FProperty* Prop = Obj->GetClass()->FindPropertyByName(FName(*Value));
|
||||
if (!Prop)
|
||||
return SetError(FString::Printf(TEXT("Property '%s' not found on %s"), *Value, *Obj->GetClass()->GetName()));
|
||||
|
||||
FObjectProperty* ObjProp = CastField<FObjectProperty>(Prop);
|
||||
if (!ObjProp)
|
||||
return SetError(FString::Printf(TEXT("Property '%s' is not an object property (type: %s)"), *Value, *Prop->GetCPPType()));
|
||||
|
||||
UObject* PropValue = ObjProp->GetObjectPropertyValue(Prop->ContainerPtrToValuePtr<void>(Obj));
|
||||
if (!PropValue)
|
||||
return SetError(FString::Printf(TEXT("Property '%s' is null on %s"), *Value, *Obj->GetName()));
|
||||
|
||||
SetObj(PropValue);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::LevelBlueprint()
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
UWorld* World = ::Cast<UWorld>(Obj);
|
||||
if (!World)
|
||||
return TypeMismatch(TEXT("levelblueprint"), TEXT("World"));
|
||||
|
||||
if (!World->PersistentLevel)
|
||||
return SetError(TEXT("World has no PersistentLevel"));
|
||||
|
||||
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
|
||||
if (!LevelBP)
|
||||
return SetError(TEXT("World has no level blueprint"));
|
||||
|
||||
SetObj(LevelBP);
|
||||
return *this;
|
||||
}
|
||||
Reference in New Issue
Block a user