diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_PrintPinType.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_PrintPinType.h deleted file mode 100644 index 43b90e72..00000000 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_PrintPinType.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "MCPServer.h" -#include "MCPHandler.h" -#include "MCPFetcher.h" -#include "MCPUtils.h" -#include "EdGraph/EdGraphNode.h" -#include "EdGraphSchema_K2.h" -#include "GraphNode_PrintPinType.generated.h" - - -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- - -UCLASS() -class UMCP_GraphNode_ListPinTypes : public UObject, public IMCPHandler -{ - GENERATED_BODY() - -public: - virtual FString GetDescription() const override - { - return TEXT("List every type available in the variable type selector menu, as Category|Item."); - } - - virtual void Handle() override - { - const UEdGraphSchema_K2* Schema = GetDefault(); - TArray> TypeTree; - Schema->GetVariableTypeTree(TypeTree, ETypeTreeFilter::None); - - for (const TSharedPtr& Root : TypeTree) - { - PrintTree(Root, FString()); - } - } - -private: - void PrintTree(const TSharedPtr& Node, const FString& Category) - { - FString Desc = Node->GetDescription().ToString(); - - if (!Node->bReadOnly) - { - UObject *SCO = Node->GetPinTypeNoResolve().PinSubCategoryObject.Get(); - if (SCO) { - UMCPServer::Printf(TEXT("SCO %s %s\n"), *SCO->GetPackage()->GetName(), *SCO->GetName()); - } else { - auto cad = Node->GetCachedAssetData(); - UMCPServer::Printf(TEXT("CAD %s %s\n"), *cad.PackageName.ToString(), *cad.AssetName.ToString()); - } - } - - FString ChildCategory = FString::Printf(TEXT("%s%s|"), *Category, *Desc); - for (const TSharedPtr& Child : Node->Children) - { - PrintTree(Child, ChildCategory); - } - } -}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/TypeName_Search.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/TypeName_Search.h new file mode 100644 index 00000000..1dd3ca09 --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/TypeName_Search.h @@ -0,0 +1,104 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPServer.h" +#include "MCPHandler.h" +#include "MCPTypes.h" +#include "MCPUtils.h" +#include "AssetRegistry/AssetData.h" +#include "AssetRegistry/IAssetRegistry.h" +#include "UObject/UObjectIterator.h" +#include "StructUtils/UserDefinedStruct.h" +#include "Engine/UserDefinedEnum.h" +#include "Engine/Blueprint.h" +#include "TypeName_Search.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UMCP_TypeName_Search : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Substring filter for type names")) + FString Query; + + UPROPERTY(meta=(Optional, Description="Maximum number of results")) + int32 Limit = 100; + + virtual FString GetDescription() const override + { + return TEXT("Search for type names usable in pin type specifications. " + "Returns short names that can be used with commands like Blueprint_ChangeVariableType."); + } + + void TryMatchObject(TSet &Matches, UObject *Obj) + { + if (!Obj) return; + FString Name = Obj->GetName(); + if (!Name.Contains(Query, ESearchCase::IgnoreCase)) return; + Matches.Add(Obj); + } + + void TryMatchObjects(TSet &Matches, UClass *Class) + { + ForEachObjectOfClass(Class, [&](UObject *Obj){ + if (Matches.Num() == Limit) return; + TryMatchObject(Matches, Obj); + }, true); + } + + void TryMatchAssets(TSet &Matches, UClass *Class) + { + IAssetRegistry& Registry = *IAssetRegistry::Get(); + TArray AssetResults; + Registry.GetAssetsByClass(Class->GetClassPathName(), AssetResults, true); + for (const FAssetData& Data : AssetResults) + { + if (Matches.Num() == Limit) return; + FString Name = Data.AssetName.ToString(); + if (!Name.Contains(Query, ESearchCase::IgnoreCase)) continue; + UObject *Obj = Data.GetAsset(); + if (UBlueprint* BP = Cast(Obj)) + Obj = BP->GeneratedClass; + TryMatchObject(Matches, Obj); + } + } + + virtual void Handle() override + { + TSet Matches; + TryMatchObjects(Matches, UScriptStruct::StaticClass()); + TryMatchObjects(Matches, UClass::StaticClass()); + TryMatchObjects(Matches, UEnum::StaticClass()); + TryMatchAssets(Matches, UBlueprint::StaticClass()); + TryMatchAssets(Matches, UUserDefinedStruct::StaticClass()); + TryMatchAssets(Matches, UUserDefinedEnum::StaticClass()); + + TArray Results; + for (const UObject *Obj : Matches) + { + const TCHAR *Kind = TEXT("Unknown"); + if (Cast(Obj)) + Kind = TEXT("Enum"); + else if (Cast(Obj)) + Kind = TEXT("Struct"); + else if (const UClass* Class = Cast(Obj)) + Kind = Class->IsChildOf(UInterface::StaticClass()) ? TEXT("Interface") : TEXT("Class"); + Results.Add(FString::Printf(TEXT("%s %s\n"), Kind, *UMCPTypes::TypeToText(Obj))); + } + Results.Sort(); + for (const auto &Result : Results) + { + UMCPServer::Print(Result); + } + if (Results.Num() == Limit) + { + UMCPServer::Printf(TEXT("Search limit reached, raise it with Limit=\n")); + } + } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp index f4091c65..b0fc4335 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp @@ -9,13 +9,27 @@ // Choose Short Name // --------------------------------------------------------------------------- +FString UMCPTypes::GetNameWithoutUnderscoreC(const UObject *Obj) +{ + FString Name = Obj->GetName(); + if (Name.EndsWith(TEXT("_C"))) + { + if (const UClass* Class = Cast(Obj)) + { + if (Class->ClassGeneratedBy != nullptr) + Name.LeftChopInline(2); + } + } + return Name; +} + void UMCPTypes::ReserveShortName(FName Name) { FString NameStr = Name.ToString(); - ShortToPath[NameStr.ToLower()] = TEXT("PRIMITIVE"); + ShortToPath.Add(NameStr.ToLower(), FString(TEXT("PRIMITIVE"))); } -FString UMCPTypes::ChooseShortName(UObject* Obj) +FString UMCPTypes::ChooseShortName(const UObject* Obj) { if (!Cast(Obj) && !Cast(Obj) && !Cast(Obj)) return FString(); @@ -24,8 +38,7 @@ FString UMCPTypes::ChooseShortName(UObject* Obj) FString *OldShort = PathToShort.Find(Path); if (OldShort != nullptr) return *OldShort; - FString Name = Obj->GetName(); - if (Name.EndsWith(TEXT("_C"))) Name.LeftChopInline(2); + FString Name = GetNameWithoutUnderscoreC(Obj); FString Lower = Name.ToLower(); if (!ShortToPath.Contains(Lower)) @@ -40,7 +53,7 @@ FString UMCPTypes::ChooseShortName(UObject* Obj) FString NumberedLower = FString::Printf(TEXT("%s%d"), *Lower, i); if (!ShortToPath.Contains(NumberedLower)) { - FString NumberedName = FString::Printf(TEXT("%s%d"), *Name, i); + FString NumberedName = FString::Printf(TEXT("%s_%d"), *Name, i); ShortToPath.Add(NumberedLower, Path); PathToShort.Add(Path, NumberedName); return NumberedName; @@ -59,31 +72,34 @@ void UMCPTypes::ChooseShortNames(UPackage* Package) }, false); } + + // --------------------------------------------------------------------------- // TypeToText // --------------------------------------------------------------------------- FString UMCPTypes::TypeToTextInner(FName Category, FName SubCategory, UObject* SubCategoryObject) { - if (Category == UEdGraphSchema_K2::PC_Boolean) return TEXT("bool"); - if (Category == UEdGraphSchema_K2::PC_Int) return TEXT("int32"); - if (Category == UEdGraphSchema_K2::PC_Int64) return TEXT("int64"); - if (Category == UEdGraphSchema_K2::PC_Name) return TEXT("Name"); - if (Category == UEdGraphSchema_K2::PC_String) return TEXT("String"); - if (Category == UEdGraphSchema_K2::PC_Text) return TEXT("Text"); + if ((Category == UEdGraphSchema_K2::PC_Boolean) || + (Category == UEdGraphSchema_K2::PC_Int) || + (Category == UEdGraphSchema_K2::PC_Int64) || + (Category == UEdGraphSchema_K2::PC_Name) || + (Category == UEdGraphSchema_K2::PC_String) || + (Category == UEdGraphSchema_K2::PC_Text)) + { + return Category.ToString(); + } if (Category == UEdGraphSchema_K2::PC_Real) { - if (SubCategory == UEdGraphSchema_K2::PC_Float) - return TEXT("float"); - return TEXT("double"); + return SubCategory.ToString(); } if (Category == UEdGraphSchema_K2::PC_Byte) { if (SubCategoryObject) return ChooseShortName(SubCategoryObject); - return TEXT("uint8"); + return Category.ToString(); } if (Category == UEdGraphSchema_K2::PC_Enum) @@ -155,6 +171,13 @@ FString UMCPTypes::TypeToText(const FProperty *Property) } } +FString UMCPTypes::TypeToText(const UObject* Obj) +{ + UMCPTypes* Types = GEditor->GetEditorSubsystem(); + if (!Types) return FString(); + return Types->ChooseShortName(Obj); +} + // --------------------------------------------------------------------------- // Subsystem lifecycle // --------------------------------------------------------------------------- diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h index 777f3f9e..05da34ca 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h @@ -22,6 +22,10 @@ public: static FString TypeToText(const FEdGraphPinType& PinType); static FString TypeToText(const FProperty *Property); + // Get the short name for a UClass, UScriptStruct, or UEnum. + // Returns empty string if the object is not one of those types. + static FString TypeToText(const UObject* Obj); + // Try to parse a type. If there's a problem, returns an error // message. If all goes well, returns empty string. static FString TryTextToType(const FString& Text, FEdGraphPinType& OutPinType); @@ -35,6 +39,10 @@ private: bool ResolvePath(const FString &Name, const FString &Path, FEdGraphPinType &OutType); + // Get the object's name, not including the _C generated + // by the blueprint compiler for generated classes. + static FString GetNameWithoutUnderscoreC(const UObject *Obj); + // Reserve the short name for a primitive type. // The value stored in the map is just "PRIMITIVE". void ReserveShortName(FName Name); @@ -43,7 +51,7 @@ private: // choose the same name for two classes. If the object already has // a short name, return it. If it's not a class, struct, enum, or // interface, return empty string. - FString ChooseShortName(UObject* Obj); + FString ChooseShortName(const UObject* Obj); // Choose short names for every type in the package. void ChooseShortNames(UPackage* Package); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h index 40d743f6..882d140f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h @@ -185,3 +185,4 @@ private: static void AppendNumericSuffix(FString &Name, int32 N); static FString SetPropertyFromJson(void* Container, FProperty* Prop, const FString& FieldName, const FJsonObject* Json); }; +