Better parsing of enums, and Blueprint_Create
This commit is contained in:
@@ -5,8 +5,10 @@
|
||||
#include "MCPHandler.h"
|
||||
#include "MCPFetcher.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "MCPTypes.h"
|
||||
#include "MCPPackageMaker.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Kismet/BlueprintFunctionLibrary.h"
|
||||
#include "Kismet2/KismetEditorUtilities.h"
|
||||
#include "Blueprint_Create.generated.h"
|
||||
|
||||
@@ -21,14 +23,14 @@ class UMCP_Blueprint_Create : public UObject, public IMCPHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Description="Full asset path for the new Blueprint (e.g. '/Game/Blueprints/BP_MyActor')"))
|
||||
UPROPERTY(meta=(Description="Full asset path for the new Blueprint"))
|
||||
FString AssetPath;
|
||||
|
||||
UPROPERTY(meta=(Description="Parent class: C++ class name or Blueprint package path"))
|
||||
UPROPERTY(meta=(Description="Parent class, expressed as a type"))
|
||||
FString ParentClass;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Blueprint type: Normal, Interface, FunctionLibrary, or MacroLibrary (default: Normal)"))
|
||||
FString BlueprintType;
|
||||
UPROPERTY(meta=(Optional, Description="Normal, Interface, FunctionLibrary, or MacroLibrary"))
|
||||
TEnumAsByte<EBlueprintType> BlueprintType = BPTYPE_Normal;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
@@ -40,35 +42,27 @@ public:
|
||||
MCPPackageMaker Maker(AssetPath);
|
||||
if (!Maker.Ok()) return;
|
||||
|
||||
// Resolve parent class — try C++ class first, then Blueprint package path
|
||||
UClass* ParentClassObj = MCPUtils::FindClassByName(ParentClass);
|
||||
|
||||
if (!ParentClassObj)
|
||||
{
|
||||
MCPFetcher F;
|
||||
UBlueprint* ParentBP = F.Asset(ParentClass).Cast<UBlueprint>();
|
||||
if (ParentBP && ParentBP->GeneratedClass)
|
||||
ParentClassObj = ParentBP->GeneratedClass;
|
||||
}
|
||||
|
||||
if (!ParentClassObj)
|
||||
{
|
||||
UMCPServer::Printf(TEXT("ERROR: Could not find parent class '%s'. Provide a C++ class name (e.g. 'Actor', 'Pawn') or Blueprint package path.\n"),
|
||||
*ParentClass);
|
||||
return;
|
||||
}
|
||||
|
||||
// Map blueprintType string to EBlueprintType
|
||||
EBlueprintType BlueprintTypeEnum = BPTYPE_Normal;
|
||||
if (!BlueprintType.IsEmpty())
|
||||
{
|
||||
if (!MCPUtils::StringToEnum(BlueprintType, BlueprintTypeEnum, TEXT("BPTYPE_"))) return;
|
||||
}
|
||||
|
||||
// For Interface type, parent must be UInterface
|
||||
if ((BlueprintTypeEnum == BPTYPE_Interface) && !ParentClassObj->IsChildOf(UInterface::StaticClass()))
|
||||
// Resolve parent class based on blueprint type
|
||||
UClass* ParentClassObj = nullptr;
|
||||
switch (BlueprintType)
|
||||
{
|
||||
case BPTYPE_Normal:
|
||||
ParentClassObj = UMCPTypes::TextToOneObjectType(ParentClass);
|
||||
if (!ParentClassObj) return;
|
||||
break;
|
||||
case BPTYPE_MacroLibrary:
|
||||
ParentClassObj = UMCPTypes::TextToOneObjectType(ParentClass);
|
||||
if (!ParentClassObj) return;
|
||||
break;
|
||||
case BPTYPE_Interface:
|
||||
ParentClassObj = UInterface::StaticClass();
|
||||
break;
|
||||
case BPTYPE_FunctionLibrary:
|
||||
ParentClassObj = UBlueprintFunctionLibrary::StaticClass();
|
||||
break;
|
||||
default:
|
||||
UMCPServer::Print(TEXT("ERROR: BlueprintType must be Normal, Interface, FunctionLibrary, or MacroLibrary\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the package and Blueprint
|
||||
@@ -78,7 +72,7 @@ public:
|
||||
ParentClassObj,
|
||||
Maker.Package(),
|
||||
FName(*Maker.Name()),
|
||||
BlueprintTypeEnum,
|
||||
BlueprintType,
|
||||
UBlueprint::StaticClass(),
|
||||
UBlueprintGeneratedClass::StaticClass()
|
||||
);
|
||||
@@ -95,10 +89,6 @@ public:
|
||||
|
||||
// Report result
|
||||
UMCPServer::Printf(TEXT("Created: %s\n"), *MCPUtils::FormatName(NewBP));
|
||||
UMCPServer::Printf(TEXT("Parent: %s\n"), *MCPUtils::FormatName(ParentClassObj));
|
||||
if (!bSaved)
|
||||
UMCPServer::Print(TEXT("Warning: save failed\n"));
|
||||
for (UEdGraph* Graph : MCPUtils::AllGraphs(NewBP))
|
||||
UMCPServer::Printf(TEXT("Graph: %s\n"), *MCPUtils::FormatName(Graph));
|
||||
if (!bSaved) UMCPServer::Print(TEXT("Warning: save failed\n"));
|
||||
}
|
||||
};
|
||||
@@ -82,5 +82,6 @@ public:
|
||||
EmitCommandList(true);
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
MCPFetcher::PrintDocs();
|
||||
UMCPTypes::PrintDocs();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -27,15 +27,23 @@ MCPFetcher::WalkFunc MCPFetcher::GetWalker(const FString& Step)
|
||||
|
||||
void MCPFetcher::PrintDocs()
|
||||
{
|
||||
UMCPServer::Print(TEXT("Some commands take a Path parameter. A Path starts with an asset\n"));
|
||||
UMCPServer::Print(TEXT("package path (e.g. /Game/Widgets/WB_Hotkeys), followed by zero or\n"));
|
||||
UMCPServer::Print(TEXT("more comma-separated steps that navigate into the asset:\n\n"));
|
||||
UMCPServer::Print(TEXT(" graph — Find a named UEdGraph (blank name for material graphs)\n"));
|
||||
UMCPServer::Print(TEXT(" node — Find a named UEdGraphNode within a graph or blueprint\n"));
|
||||
UMCPServer::Print(TEXT(" pin — Find a named UEdGraphPin on a node\n"));
|
||||
UMCPServer::Print(TEXT(" component — Find a named component in a Blueprint's SCS\n"));
|
||||
UMCPServer::Print(TEXT(" levelblueprint — Get the level blueprint from a UWorld\n"));
|
||||
UMCPServer::Print(TEXT("\nExample: /Game/Widgets/WB_Hotkeys,graph:EventGraph,node:Self_Reference_03,pin:Result\n"));
|
||||
UMCPServer::Print(TEXT("Most commands require you to specify a path. A path starts\n"));
|
||||
UMCPServer::Print(TEXT("with an asset name, followed by comma-separated steps that\n"));
|
||||
UMCPServer::Print(TEXT("navigate into the asset. Example:\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
UMCPServer::Print(TEXT(" /Game/Widgets/WB_Hotkeys,graph:EventGraph,node:Self03,pin:Result\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
UMCPServer::Print(TEXT("The navigation steps supported are:\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
UMCPServer::Print(TEXT(" graph — move from a blueprint or material to a graph.\n"));
|
||||
UMCPServer::Print(TEXT(" node — move from a graph to a graph node\n"));
|
||||
UMCPServer::Print(TEXT(" pin — move from a graph node to a pin\n"));
|
||||
UMCPServer::Print(TEXT(" component — move from a blueprint to a component\n"));
|
||||
UMCPServer::Print(TEXT(" levelblueprint — move from a world to a blueprint\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
UMCPServer::Print(TEXT("It is often useful to use 'dump' commands to see the contents\n"));
|
||||
UMCPServer::Print(TEXT("of an asset, so you know what objects exist to navigate into.\n"));
|
||||
UMCPServer::Print(TEXT("\n"));
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "Materials/MaterialExpression.h"
|
||||
#include "MaterialGraph/MaterialGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "UObject/EnumProperty.h"
|
||||
|
||||
static bool IsPinTypeProperty(FProperty* Prop)
|
||||
{
|
||||
@@ -26,18 +27,74 @@ FString MCPProperty::GetText() const
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool MCPProperty::SetText(const FString& Value)
|
||||
bool MCPProperty::TryParseEnum(UEnum* Enum, const FString& Text, int64 &OutValue)
|
||||
{
|
||||
int Index = Enum->GetIndexByNameString(Text);
|
||||
if (Index == INDEX_NONE)
|
||||
{
|
||||
FString Prefix = Enum->GenerateEnumPrefix();
|
||||
if (!Prefix.IsEmpty())
|
||||
{
|
||||
Index = Enum->GetIndexByNameString(Prefix + TEXT("_") + Text);
|
||||
}
|
||||
}
|
||||
if (Index == INDEX_NONE)
|
||||
{
|
||||
UMCPServer::Printf(TEXT("ERROR: '%s' is not a valid value for %s\n"),
|
||||
*Text, *Enum->GetName());
|
||||
OutValue = 0;
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
OutValue = Enum->GetValueByIndex(Index);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool MCPProperty::TrySetText(const FString &Value)
|
||||
{
|
||||
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||
|
||||
// Pin types get parsed by UMCPTypes.
|
||||
if (IsPinTypeProperty(Prop))
|
||||
return UMCPTypes::TextToType(Value, *static_cast<FEdGraphPinType*>(ValuePtr));
|
||||
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, nullptr, PPF_None);
|
||||
if (!ImportResult)
|
||||
|
||||
// Byte Enum types get parsed by TryParseEnum, above.
|
||||
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
|
||||
{
|
||||
if (UEnum* Enum = ByteProp->Enum)
|
||||
{
|
||||
int64 EnumValue;
|
||||
if (!TryParseEnum(Enum, Value, EnumValue)) return false;
|
||||
ByteProp->SetPropertyValue(ValuePtr, (uint8)EnumValue);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Regular Enum types get parsed by TryParseEnum, above.
|
||||
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
|
||||
{
|
||||
int64 EnumValue;
|
||||
if (!TryParseEnum(EnumProp->GetEnum(), Value, EnumValue)) return false;
|
||||
EnumProp->GetUnderlyingProperty()->SetIntPropertyValue(ValuePtr, EnumValue);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Non-enum properties use ImportText
|
||||
const TCHAR* Result = Prop->ImportText_Direct(*Value, ValuePtr, nullptr, PPF_None);
|
||||
if (!Result)
|
||||
{
|
||||
UMCPServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
|
||||
*Value, *MCPUtils::FormatName(Prop), *Prop->GetCPPType());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MCPProperty::SetText(const FString& Value)
|
||||
{
|
||||
if (!TrySetText(Value)) return false;
|
||||
|
||||
if (Prop->GetOwnerClass()->IsChildOf(UMaterialExpression::StaticClass()))
|
||||
{
|
||||
|
||||
@@ -494,4 +494,47 @@ bool UMCPTypes::TextToType(const FString& Text, FEdGraphPinType& OutPinType)
|
||||
return false;
|
||||
}
|
||||
|
||||
UClass* UMCPTypes::TextToOneObjectType(const FString& Text)
|
||||
{
|
||||
FEdGraphPinType PinType;
|
||||
if (!TextToType(Text, PinType)) return nullptr;
|
||||
UClass* Class = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
if ((!Class) || (PinType.PinCategory != UEdGraphSchema_K2::PC_Object) ||
|
||||
(PinType.IsContainer()))
|
||||
{
|
||||
UMCPServer::Printf(TEXT("ERROR: '%s' is not a plain object class\n"), *Text);
|
||||
return nullptr;
|
||||
}
|
||||
return Class;
|
||||
}
|
||||
|
||||
UClass* UMCPTypes::TextToOneInterfaceType(const FString& Text)
|
||||
{
|
||||
FEdGraphPinType PinType;
|
||||
if (!TextToType(Text, PinType)) return nullptr;
|
||||
UClass* Class = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
if ((!Class) || (PinType.PinCategory != UEdGraphSchema_K2::PC_Interface) ||
|
||||
(PinType.IsContainer()))
|
||||
{
|
||||
UMCPServer::Printf(TEXT("ERROR: '%s' is not an interface class\n"), *Text);
|
||||
return nullptr;
|
||||
}
|
||||
return Class;
|
||||
}
|
||||
|
||||
void UMCPTypes::PrintDocs()
|
||||
{
|
||||
UMCPServer::Printf(TEXT("To express types, use case-insensitive short names:\n"));
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
UMCPServer::Printf(TEXT(" boolean, int64, double, string, etc\n"));
|
||||
UMCPServer::Printf(TEXT(" vector, rotator, hitresult, etc\n"));
|
||||
UMCPServer::Printf(TEXT(" actor, character, playercontroller, etc\n"));
|
||||
UMCPServer::Printf(TEXT(" eblendmode, emovementmode, etc\n"));
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
UMCPServer::Printf(TEXT("Notice that it's 'actor', not 'AActor'.\n"));
|
||||
UMCPServer::Printf(TEXT("You can use the following notations for complex types:\n"));
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
UMCPServer::Printf(TEXT(" soft<abp_manny>, class<pawn>, softclass<pawn>\n"));
|
||||
UMCPServer::Printf(TEXT(" array<int>, set<string>, map<int, string>\n"));
|
||||
UMCPServer::Printf(TEXT("\n"));
|
||||
}
|
||||
@@ -28,5 +28,7 @@ public:
|
||||
static MCPProperty GetOneExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name);
|
||||
|
||||
private:
|
||||
bool TryParseEnum(UEnum* Enum, const FString& Text, int64 &OutValue);
|
||||
bool TrySetText(const FString &Text);
|
||||
static void Collect(UStruct* StructType, void* Container, TArray<MCPProperty> &Props, EPropertyFlags Flags);
|
||||
};
|
||||
|
||||
@@ -48,9 +48,6 @@ public:
|
||||
/** Track an object that has been modified by the current handler. */
|
||||
static void AddTouchedObject(UObject* Obj) { GMCPServer->Notifier.AddTouchedObject(Obj); }
|
||||
|
||||
/** Send notifications now (also called automatically after the handler returns). */
|
||||
static void SendNotifications() { GMCPServer->Notifier.SendNotifications(); }
|
||||
|
||||
/** Print text to the handler output buffer. */
|
||||
static void Print(const TCHAR* Text) { GMCPServer->HandlerOutput.Append(Text); }
|
||||
static void Print(const FString& Text) { GMCPServer->HandlerOutput.Append(Text); }
|
||||
|
||||
@@ -34,6 +34,17 @@ public:
|
||||
// via UMCPServer and returns false.
|
||||
static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
||||
|
||||
// Parse a type string and verify it's a single object class (PC_Object,
|
||||
// no container, no wrapper). Returns nullptr and prints error on failure.
|
||||
static UClass* TextToOneObjectType(const FString& Text);
|
||||
|
||||
// Parse a type string and verify it's a single interface class (PC_Interface,
|
||||
// no container, no wrapper). Returns nullptr and prints error on failure.
|
||||
static UClass* TextToOneInterfaceType(const FString& Text);
|
||||
|
||||
// Print the documentation for how types are expressed, for the LLM.
|
||||
static void PrintDocs();
|
||||
|
||||
private:
|
||||
FString TypeToTextInner(FName Category, FName SubCategory, UObject* SubCategoryObject);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user