Start thinking about types
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "MCPServer.h"
|
||||
#include "MCPHandler.h"
|
||||
#include "MCPAssets.h"
|
||||
#include "MCPFetcher.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Kismet2/KismetEditorUtilities.h"
|
||||
@@ -20,124 +20,35 @@ class UMCP_Blueprint_Compile : public UObject, public IMCPHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Optional, Description="Blueprint name or package path. If specified, compile this single blueprint."))
|
||||
FString Blueprint;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Substring search query. If specified instead of blueprint, compile all matching blueprints."))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="If true, return the count of matching blueprints without compiling."))
|
||||
bool CountOnly = false;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Starting index for pagination (default 0)."))
|
||||
int32 Offset = 0;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Maximum number of blueprints to compile (default 0 = no limit)."))
|
||||
int32 Limit = 0;
|
||||
UPROPERTY(meta=(Optional, Description="Path of the blueprint."))
|
||||
FString Path;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
return TEXT("Compile one or more blueprints without saving. "
|
||||
"Reports errors and warnings. "
|
||||
"Use 'blueprint' for a single blueprint, or 'query' to bulk-compile matching blueprints.");
|
||||
return TEXT("Compile a blueprint. ");
|
||||
}
|
||||
|
||||
// Compile a single blueprint and append results to Out.
|
||||
// Returns true if the blueprint compiled cleanly (no errors).
|
||||
static bool CompileAndReport(UBlueprint* BP)
|
||||
virtual void Handle() override
|
||||
{
|
||||
MCPFetcher F;
|
||||
UBlueprint *BP = F.Walk(Path).Cast<UBlueprint>();
|
||||
|
||||
EBlueprintCompileOptions CompileOpts =
|
||||
EBlueprintCompileOptions::SkipSave |
|
||||
EBlueprintCompileOptions::SkipGarbageCollection |
|
||||
EBlueprintCompileOptions::SkipFiBSearchMetaUpdate;
|
||||
EBlueprintCompileOptions::SkipSave |
|
||||
EBlueprintCompileOptions::SkipGarbageCollection |
|
||||
EBlueprintCompileOptions::SkipFiBSearchMetaUpdate;
|
||||
|
||||
FKismetEditorUtilities::CompileBlueprint(BP, CompileOpts, nullptr);
|
||||
|
||||
int32 ErrorCount = 0;
|
||||
int32 WarningCount = 0;
|
||||
|
||||
// Collect compiler messages from nodes
|
||||
for (UEdGraphNode* Node : MCPUtils::AllNodes(BP))
|
||||
{
|
||||
if (!Node->bHasCompilerMessage) continue;
|
||||
bool bIsError = (Node->ErrorType == EMessageSeverity::Error);
|
||||
if (bIsError) ErrorCount++; else WarningCount++;
|
||||
UMCPServer::Printf(TEXT(" %s: [%s] %s > %s: %s\n"),
|
||||
bIsError ? TEXT("ERROR") : TEXT("WARNING"),
|
||||
UMCPServer::Printf(TEXT("%s %s: %s\n"),
|
||||
*MCPUtils::FormatName(Node->GetGraph()),
|
||||
*MCPUtils::FormatName(Node),
|
||||
*MCPUtils::FormatName(Node->GetClass()),
|
||||
*Node->ErrorMsg);
|
||||
}
|
||||
|
||||
FString StatusStr = MCPUtils::EnumToString((EBlueprintStatus)BP->Status, TEXT("BS_"));
|
||||
bool bIsValid = (BP->Status == BS_UpToDate) && (ErrorCount == 0);
|
||||
|
||||
if (bIsValid && WarningCount == 0)
|
||||
{
|
||||
UMCPServer::Printf(TEXT(" OK (status: %s)\n"), *StatusStr);
|
||||
}
|
||||
else
|
||||
{
|
||||
UMCPServer::Printf(TEXT(" status: %s, errors: %d, warnings: %d\n"),
|
||||
*StatusStr, ErrorCount, WarningCount);
|
||||
}
|
||||
|
||||
return bIsValid;
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
MCPAssets<UBlueprint> Finder;
|
||||
Finder.Scan<UBlueprint>().Scan<UWorld>();
|
||||
if (!Blueprint.IsEmpty())
|
||||
{
|
||||
if (!Finder.Exact(Blueprint).ENone().ETwo().Info()) return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!Finder.Substring(Query).Info()) return;
|
||||
}
|
||||
|
||||
const TArray<FAssetData>& MatchingAssets = Finder.AllData();
|
||||
int32 TotalMatching = MatchingAssets.Num();
|
||||
|
||||
// countOnly: return count without compiling anything
|
||||
if (CountOnly)
|
||||
{
|
||||
UMCPServer::Printf(TEXT("Matching blueprints: %d\n"), TotalMatching);
|
||||
return;
|
||||
}
|
||||
|
||||
// Compute range
|
||||
int32 StartIdx = FMath::Clamp(Offset, 0, TotalMatching);
|
||||
int32 EndIdx = (Limit > 0) ? FMath::Min(StartIdx + Limit, TotalMatching) : TotalMatching;
|
||||
|
||||
int32 TotalChecked = 0;
|
||||
int32 TotalPassed = 0;
|
||||
int32 TotalFailed = 0;
|
||||
|
||||
for (int32 Idx = StartIdx; Idx < EndIdx; Idx++)
|
||||
{
|
||||
const FAssetData& Asset = MatchingAssets[Idx];
|
||||
FString PackagePath = Asset.PackageName.ToString();
|
||||
|
||||
// Load the Blueprint (handles both regular and level blueprints)
|
||||
MCPAssets<UBlueprint> Loader;
|
||||
Loader.Scan<UBlueprint>().Scan<UWorld>();
|
||||
if (!Loader.Exact(PackagePath).ENone().ETwo().Load())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UBlueprint* BP = Loader.Object();
|
||||
|
||||
TotalChecked++;
|
||||
UMCPServer::Printf(TEXT("%s:\n"), *MCPUtils::FormatName(BP));
|
||||
bool bValid = CompileAndReport(BP);
|
||||
if (bValid) TotalPassed++; else TotalFailed++;
|
||||
}
|
||||
|
||||
UMCPServer::Printf(TEXT("\nSummary: %d checked, %d passed, %d failed (of %d matching)\n"),
|
||||
TotalChecked, TotalPassed, TotalFailed, TotalMatching);
|
||||
UMCPServer::Printf(TEXT("Compilation Done.\n"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
#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<UEdGraphSchema_K2>();
|
||||
TArray<TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo>> TypeTree;
|
||||
Schema->GetVariableTypeTree(TypeTree, ETypeTreeFilter::None);
|
||||
|
||||
for (const TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo>& Root : TypeTree)
|
||||
{
|
||||
PrintTree(Root, FString());
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void PrintTree(const TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo>& Node, const FString& Category)
|
||||
{
|
||||
FString Desc = Node->GetDescription().ToString();
|
||||
|
||||
if (!Node->bReadOnly)
|
||||
{
|
||||
UObject *SCO = Node->GetPinTypeNoResolve().PinSubCategoryObject.Get();
|
||||
FString Path = SCO ? SCO->GetPathName() : *Node->GetCachedAssetData().GetObjectPathString();
|
||||
UMCPServer::Printf(TEXT("%s%s --- %s\n"), *Category, *Desc, *Path);
|
||||
}
|
||||
|
||||
FString ChildCategory = FString::Printf(TEXT("%s%s|"), *Category, *Desc);
|
||||
for (const TSharedPtr<UEdGraphSchema_K2::FPinTypeTreeInfo>& Child : Node->Children)
|
||||
{
|
||||
PrintTree(Child, ChildCategory);
|
||||
}
|
||||
}
|
||||
};
|
||||
140
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp
Normal file
140
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPTypes.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
#include "MCPTypes.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helper: format a UField name with module prefix if not in /Script/Engine.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static FString FormatFieldName(UField* Field, const FString& TypePrefix)
|
||||
{
|
||||
// Package name is /Script/ModuleName
|
||||
FString ModuleName = Field->GetOutermost()->GetName().Mid(8);
|
||||
|
||||
FString Name = TypePrefix + Field->GetName();
|
||||
if (ModuleName != TEXT("Engine"))
|
||||
return ModuleName + TEXT("::") + Name;
|
||||
return Name;
|
||||
}
|
||||
|
||||
static FString FormatClassName(UClass* Class)
|
||||
{
|
||||
FString Prefix = Class->IsChildOf(AActor::StaticClass()) ? TEXT("A") : TEXT("U");
|
||||
return FormatFieldName(Class, Prefix);
|
||||
}
|
||||
|
||||
static FString FormatStructName(UScriptStruct* Struct)
|
||||
{
|
||||
return FormatFieldName(Struct, TEXT("F"));
|
||||
}
|
||||
|
||||
static FString FormatEnumName(UEnum* Enum)
|
||||
{
|
||||
return FormatFieldName(Enum, TEXT("E"));
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// TypeToText
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static FString TerminalToText(const FEdGraphPinType& PinType)
|
||||
{
|
||||
const FName& Cat = PinType.PinCategory;
|
||||
UObject* SubObj = PinType.PinSubCategoryObject.Get();
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_Boolean) return TEXT("bool");
|
||||
if (Cat == UEdGraphSchema_K2::PC_Int) return TEXT("int32");
|
||||
if (Cat == UEdGraphSchema_K2::PC_Int64) return TEXT("int64");
|
||||
if (Cat == UEdGraphSchema_K2::PC_Name) return TEXT("FName");
|
||||
if (Cat == UEdGraphSchema_K2::PC_String) return TEXT("FString");
|
||||
if (Cat == UEdGraphSchema_K2::PC_Text) return TEXT("FText");
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_Real)
|
||||
{
|
||||
if (PinType.PinSubCategory == UEdGraphSchema_K2::PC_Float)
|
||||
return TEXT("float");
|
||||
return TEXT("double");
|
||||
}
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_Byte)
|
||||
{
|
||||
if (UEnum* Enum = Cast<UEnum>(SubObj))
|
||||
return FormatEnumName(Enum);
|
||||
return TEXT("uint8");
|
||||
}
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_Enum)
|
||||
{
|
||||
if (UEnum* Enum = Cast<UEnum>(SubObj))
|
||||
return FormatEnumName(Enum);
|
||||
return TEXT("uint8");
|
||||
}
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_Struct)
|
||||
{
|
||||
if (UScriptStruct* Struct = Cast<UScriptStruct>(SubObj))
|
||||
return FormatStructName(Struct);
|
||||
return FString();
|
||||
}
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_Object)
|
||||
{
|
||||
if (UClass* Class = Cast<UClass>(SubObj))
|
||||
return FormatClassName(Class) + TEXT("*");
|
||||
return FString();
|
||||
}
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_Class)
|
||||
{
|
||||
if (UClass* Class = Cast<UClass>(SubObj))
|
||||
return FString::Printf(TEXT("TSubclassOf<%s>"), *FormatClassName(Class));
|
||||
return FString();
|
||||
}
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_SoftObject)
|
||||
{
|
||||
if (UClass* Class = Cast<UClass>(SubObj))
|
||||
return FString::Printf(TEXT("TSoftObjectPtr<%s>"), *FormatClassName(Class));
|
||||
return FString();
|
||||
}
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_SoftClass)
|
||||
{
|
||||
if (UClass* Class = Cast<UClass>(SubObj))
|
||||
return FString::Printf(TEXT("TSoftClassPtr<%s>"), *FormatClassName(Class));
|
||||
return FString();
|
||||
}
|
||||
|
||||
if (Cat == UEdGraphSchema_K2::PC_Interface)
|
||||
{
|
||||
if (UClass* Class = Cast<UClass>(SubObj))
|
||||
return FString::Printf(TEXT("TScriptInterface<%s>"), *FormatClassName(Class));
|
||||
return FString();
|
||||
}
|
||||
|
||||
return FString();
|
||||
}
|
||||
|
||||
FString MCPTypes::TypeToText(const FEdGraphPinType& PinType)
|
||||
{
|
||||
FString Inner = TerminalToText(PinType);
|
||||
if (Inner.IsEmpty())
|
||||
return FString();
|
||||
|
||||
if (PinType.IsArray())
|
||||
return FString::Printf(TEXT("TArray<%s>"), *Inner);
|
||||
if (PinType.IsSet())
|
||||
return FString::Printf(TEXT("TSet<%s>"), *Inner);
|
||||
if (PinType.IsMap())
|
||||
{
|
||||
FEdGraphPinType ValueType;
|
||||
ValueType.PinCategory = PinType.PinValueType.TerminalCategory;
|
||||
ValueType.PinSubCategory = PinType.PinValueType.TerminalSubCategory;
|
||||
ValueType.PinSubCategoryObject = PinType.PinValueType.TerminalSubCategoryObject;
|
||||
FString ValueInner = TerminalToText(ValueType);
|
||||
if (ValueInner.IsEmpty())
|
||||
return FString();
|
||||
return FString::Printf(TEXT("TMap<%s, %s>"), *Inner, *ValueInner);
|
||||
}
|
||||
|
||||
return Inner;
|
||||
}
|
||||
46
Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h
Normal file
46
Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPTypes.h
Normal file
@@ -0,0 +1,46 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MCPTypes — converts between FEdGraphPinType and a concise C++-like string.
|
||||
//
|
||||
// The text format mirrors GetCPPType where possible:
|
||||
// bool, uint8, int32, int64, float, double, FName, FString, FText
|
||||
// FVector, FRotator, FTransform, ... (structs)
|
||||
// EBlendMode, ... (enums)
|
||||
// AActor*, UStaticMesh*, ... (object references)
|
||||
// TSubclassOf<AActor> (class references)
|
||||
// TSoftObjectPtr<AActor> (soft object references)
|
||||
// TSoftClassPtr<AActor> (soft class references)
|
||||
// TScriptInterface<IMyInterface> (interfaces)
|
||||
// TArray<float>, TSet<FName>, TMap<FString, int32> (containers)
|
||||
//
|
||||
// TypeToText and TextToType are inverses of each other.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class MCPTypes
|
||||
{
|
||||
public:
|
||||
// Convert an FEdGraphPinType to a concise string.
|
||||
// Returns empty string on failure.
|
||||
FString TypeToText(const FEdGraphPinType& PinType);
|
||||
|
||||
// Convert a concise string back to an FEdGraphPinType.
|
||||
// Returns true on success. Prints error via UMCPServer on failure.
|
||||
bool TextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
||||
|
||||
// Same as TextToType, but does not print an error message on failure.
|
||||
bool TryTextToType(const FString& Text, FEdGraphPinType& OutPinType);
|
||||
|
||||
private:
|
||||
TArray<FString> Tokens;
|
||||
int32 Cursor = 0;
|
||||
|
||||
};
|
||||
Reference in New Issue
Block a user