Ported UserTypes handlers over to new MCP handlers
This commit is contained in:
@@ -63,21 +63,26 @@ UMCPAssetFinder* UMCPAssetFinder::GetUpdatedAssets()
|
|||||||
Self->AllMaterialAssets.Empty();
|
Self->AllMaterialAssets.Empty();
|
||||||
Self->AllMaterialInstanceAssets.Empty();
|
Self->AllMaterialInstanceAssets.Empty();
|
||||||
Self->AllMaterialFunctionAssets.Empty();
|
Self->AllMaterialFunctionAssets.Empty();
|
||||||
|
Self->AllStructAssets.Empty();
|
||||||
|
Self->AllEnumAssets.Empty();
|
||||||
|
|
||||||
AR.GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), Self->AllBlueprintAssets, true);
|
AR.GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), Self->AllBlueprintAssets, true);
|
||||||
AR.GetAssetsByClass(UWorld::StaticClass()->GetClassPathName(), Self->AllMapAssets, false);
|
AR.GetAssetsByClass(UWorld::StaticClass()->GetClassPathName(), Self->AllMapAssets, false);
|
||||||
AR.GetAssetsByClass(UMaterial::StaticClass()->GetClassPathName(), Self->AllMaterialAssets, false);
|
AR.GetAssetsByClass(UMaterial::StaticClass()->GetClassPathName(), Self->AllMaterialAssets, false);
|
||||||
AR.GetAssetsByClass(UMaterialInstanceConstant::StaticClass()->GetClassPathName(), Self->AllMaterialInstanceAssets, false);
|
AR.GetAssetsByClass(UMaterialInstanceConstant::StaticClass()->GetClassPathName(), Self->AllMaterialInstanceAssets, false);
|
||||||
AR.GetAssetsByClass(UMaterialFunction::StaticClass()->GetClassPathName(), Self->AllMaterialFunctionAssets, false);
|
AR.GetAssetsByClass(UMaterialFunction::StaticClass()->GetClassPathName(), Self->AllMaterialFunctionAssets, false);
|
||||||
|
AR.GetAssetsByClass(UUserDefinedStruct::StaticClass()->GetClassPathName(), Self->AllStructAssets, false);
|
||||||
|
AR.GetAssetsByClass(UUserDefinedEnum::StaticClass()->GetClassPathName(), Self->AllEnumAssets, false);
|
||||||
|
|
||||||
Self->AllBlueprintAndMapAssets = Self->AllBlueprintAssets;
|
Self->AllBlueprintAndMapAssets = Self->AllBlueprintAssets;
|
||||||
Self->AllBlueprintAndMapAssets.Append(Self->AllMapAssets);
|
Self->AllBlueprintAndMapAssets.Append(Self->AllMapAssets);
|
||||||
|
|
||||||
Self->bDirty = false;
|
Self->bDirty = false;
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("MCPAssetFinder: Refreshed — BP %d, Map %d, Mat %d, MI %d, MF %d"),
|
UE_LOG(LogTemp, Display, TEXT("MCPAssetFinder: Refreshed — BP %d, Map %d, Mat %d, MI %d, MF %d, Struct %d, Enum %d"),
|
||||||
Self->AllBlueprintAssets.Num(), Self->AllMapAssets.Num(), Self->AllMaterialAssets.Num(),
|
Self->AllBlueprintAssets.Num(), Self->AllMapAssets.Num(), Self->AllMaterialAssets.Num(),
|
||||||
Self->AllMaterialInstanceAssets.Num(), Self->AllMaterialFunctionAssets.Num());
|
Self->AllMaterialInstanceAssets.Num(), Self->AllMaterialFunctionAssets.Num(),
|
||||||
|
Self->AllStructAssets.Num(), Self->AllEnumAssets.Num());
|
||||||
|
|
||||||
return Self;
|
return Self;
|
||||||
}
|
}
|
||||||
@@ -122,6 +127,18 @@ const TArray<FAssetData>& UMCPAssetFinder::GetMaterialFunctionAssets()
|
|||||||
return Self ? Self->AllMaterialFunctionAssets : EmptyAssetArray;
|
return Self ? Self->AllMaterialFunctionAssets : EmptyAssetArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TArray<FAssetData>& UMCPAssetFinder::GetStructAssets()
|
||||||
|
{
|
||||||
|
UMCPAssetFinder* Self = GetUpdatedAssets();
|
||||||
|
return Self ? Self->AllStructAssets : EmptyAssetArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TArray<FAssetData>& UMCPAssetFinder::GetEnumAssets()
|
||||||
|
{
|
||||||
|
UMCPAssetFinder* Self = GetUpdatedAssets();
|
||||||
|
return Self ? Self->AllEnumAssets : EmptyAssetArray;
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Find helpers (search cached lists by name or path)
|
// Find helpers (search cached lists by name or path)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -204,6 +221,16 @@ FAssetData* UMCPAssetFinder::FindMaterialFunctionAsset(const FString& NameOrPath
|
|||||||
return FindInList(GetMaterialFunctionAssets(), NameOrPath, OutError);
|
return FindInList(GetMaterialFunctionAssets(), NameOrPath, OutError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FAssetData* UMCPAssetFinder::FindStructAsset(const FString& NameOrPath, FString* OutError)
|
||||||
|
{
|
||||||
|
return FindInList(GetStructAssets(), NameOrPath, OutError);
|
||||||
|
}
|
||||||
|
|
||||||
|
FAssetData* UMCPAssetFinder::FindEnumAsset(const FString& NameOrPath, FString* OutError)
|
||||||
|
{
|
||||||
|
return FindInList(GetEnumAssets(), NameOrPath, OutError);
|
||||||
|
}
|
||||||
|
|
||||||
FAssetData* UMCPAssetFinder::FindAnyAsset(const FString& NameOrPath, FString* OutError)
|
FAssetData* UMCPAssetFinder::FindAnyAsset(const FString& NameOrPath, FString* OutError)
|
||||||
{
|
{
|
||||||
FAssetData* Asset = FindBlueprintAsset(NameOrPath, OutError);
|
FAssetData* Asset = FindBlueprintAsset(NameOrPath, OutError);
|
||||||
@@ -289,3 +316,29 @@ UMaterialFunction* UMCPAssetFinder::LoadMaterialFunctionByName(const FString& Na
|
|||||||
OutError = FString::Printf(TEXT("Material Function '%s' not found. Use list_material_functions to see available assets."), *NameOrPath);
|
OutError = FString::Printf(TEXT("Material Function '%s' not found. Use list_material_functions to see available assets."), *NameOrPath);
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UUserDefinedStruct* UMCPAssetFinder::LoadStructByName(const FString& NameOrPath, FString& OutError)
|
||||||
|
{
|
||||||
|
FAssetData* Asset = FindStructAsset(NameOrPath, &OutError);
|
||||||
|
if (Asset)
|
||||||
|
{
|
||||||
|
UUserDefinedStruct* Struct = Cast<UUserDefinedStruct>(Asset->GetAsset());
|
||||||
|
if (Struct) return Struct;
|
||||||
|
}
|
||||||
|
if (OutError.IsEmpty())
|
||||||
|
OutError = FString::Printf(TEXT("UserDefinedStruct '%s' not found."), *NameOrPath);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
UUserDefinedEnum* UMCPAssetFinder::LoadEnumByName(const FString& NameOrPath, FString& OutError)
|
||||||
|
{
|
||||||
|
FAssetData* Asset = FindEnumAsset(NameOrPath, &OutError);
|
||||||
|
if (Asset)
|
||||||
|
{
|
||||||
|
UUserDefinedEnum* Enum = Cast<UUserDefinedEnum>(Asset->GetAsset());
|
||||||
|
if (Enum) return Enum;
|
||||||
|
}
|
||||||
|
if (OutError.IsEmpty())
|
||||||
|
OutError = FString::Printf(TEXT("UserDefinedEnum '%s' not found."), *NameOrPath);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|||||||
@@ -4,3 +4,4 @@
|
|||||||
#include "MCPHandlers_PinMutation.h"
|
#include "MCPHandlers_PinMutation.h"
|
||||||
#include "MCPHandlers_AssetMutation.h"
|
#include "MCPHandlers_AssetMutation.h"
|
||||||
#include "MCPHandlers_Validation.h"
|
#include "MCPHandlers_Validation.h"
|
||||||
|
#include "MCPHandlers_UserTypes.h"
|
||||||
|
|||||||
@@ -1,386 +0,0 @@
|
|||||||
#include "MCPServer.h"
|
|
||||||
#include "MCPUtils.h"
|
|
||||||
#include "Engine/UserDefinedStruct.h"
|
|
||||||
#include "Engine/UserDefinedEnum.h"
|
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
|
||||||
#include "UserDefinedStructure/UserDefinedStructEditorData.h"
|
|
||||||
#include "Kismet2/EnumEditorUtils.h"
|
|
||||||
#include "Serialization/JsonReader.h"
|
|
||||||
#include "Serialization/JsonWriter.h"
|
|
||||||
#include "Serialization/JsonSerializer.h"
|
|
||||||
#include "UObject/SavePackage.h"
|
|
||||||
#include "Misc/PackageName.h"
|
|
||||||
#include "AssetRegistry/AssetRegistryModule.h"
|
|
||||||
#include "AssetRegistry/IAssetRegistry.h"
|
|
||||||
#include "AssetToolsModule.h"
|
|
||||||
#include "IAssetTools.h"
|
|
||||||
#include "Factories/StructureFactory.h"
|
|
||||||
#include "Factories/EnumFactory.h"
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// HandleCreateStruct — create a new UserDefinedStruct asset
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
void FBlueprintMCPServer::HandleCreateStruct(const FJsonObject* Json, FJsonObject* Result)
|
|
||||||
{
|
|
||||||
FString AssetPath = Json->GetStringField(TEXT("assetPath"));
|
|
||||||
if (AssetPath.IsEmpty())
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/S_MyStruct')"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split path into package path and asset name
|
|
||||||
FString PackagePath, AssetName;
|
|
||||||
int32 LastSlash;
|
|
||||||
if (AssetPath.FindLastChar('/', LastSlash))
|
|
||||||
{
|
|
||||||
PackagePath = AssetPath.Left(LastSlash);
|
|
||||||
AssetName = AssetPath.Mid(LastSlash + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/S_MyStruct')"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AssetName.IsEmpty())
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Invalid asset name in assetPath"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if asset already exists
|
|
||||||
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
|
||||||
FAssetData ExistingAsset = ARM.Get().GetAssetByObjectPath(FSoftObjectPath(AssetPath + TEXT(".") + AssetName));
|
|
||||||
if (ExistingAsset.IsValid())
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Asset already exists at '%s'"), *AssetPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the struct using the AssetTools factory
|
|
||||||
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
|
||||||
IAssetTools& AssetTools = AssetToolsModule.Get();
|
|
||||||
|
|
||||||
UStructureFactory* Factory = NewObject<UStructureFactory>();
|
|
||||||
UObject* NewAsset = AssetTools.CreateAsset(AssetName, PackagePath, UUserDefinedStruct::StaticClass(), Factory);
|
|
||||||
|
|
||||||
if (!NewAsset)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create UserDefinedStruct asset"));
|
|
||||||
}
|
|
||||||
|
|
||||||
UUserDefinedStruct* NewStruct = Cast<UUserDefinedStruct>(NewAsset);
|
|
||||||
if (!NewStruct)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Created asset is not a UserDefinedStruct"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add properties if specified
|
|
||||||
const TArray<TSharedPtr<FJsonValue>>* PropsArray = nullptr;
|
|
||||||
int32 PropsAdded = 0;
|
|
||||||
if (Json->TryGetArrayField(TEXT("properties"), PropsArray) && PropsArray)
|
|
||||||
{
|
|
||||||
for (const TSharedPtr<FJsonValue>& PropVal : *PropsArray)
|
|
||||||
{
|
|
||||||
TSharedPtr<FJsonObject> PropObj = PropVal->AsObject();
|
|
||||||
if (!PropObj) continue;
|
|
||||||
|
|
||||||
FString PropName = PropObj->GetStringField(TEXT("name"));
|
|
||||||
FString PropType = PropObj->GetStringField(TEXT("type"));
|
|
||||||
if (PropName.IsEmpty() || PropType.IsEmpty()) continue;
|
|
||||||
|
|
||||||
FEdGraphPinType PinType;
|
|
||||||
FString TypeError;
|
|
||||||
if (!MCPUtils::ResolveTypeFromString(PropType, PinType, TypeError))
|
|
||||||
{
|
|
||||||
UE_LOG(LogTemp, Warning, TEXT("BlueprintMCP: Could not resolve type '%s' for property '%s': %s"), *PropType, *PropName, *TypeError);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshot existing GUIDs so we can find the newly added one
|
|
||||||
TSet<FGuid> ExistingGuids;
|
|
||||||
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(NewStruct))
|
|
||||||
{
|
|
||||||
ExistingGuids.Add(Var.VarGuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bAdded = FStructureEditorUtils::AddVariable(NewStruct, PinType);
|
|
||||||
if (bAdded)
|
|
||||||
{
|
|
||||||
// Find the new variable by diffing GUID sets
|
|
||||||
FGuid NewPropGuid;
|
|
||||||
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(NewStruct))
|
|
||||||
{
|
|
||||||
if (!ExistingGuids.Contains(Var.VarGuid))
|
|
||||||
{
|
|
||||||
NewPropGuid = Var.VarGuid;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (NewPropGuid.IsValid())
|
|
||||||
{
|
|
||||||
FStructureEditorUtils::RenameVariable(NewStruct, NewPropGuid, PropName);
|
|
||||||
}
|
|
||||||
PropsAdded++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save
|
|
||||||
UPackage* Package = NewStruct->GetPackage();
|
|
||||||
FString PackageFilename = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension());
|
|
||||||
FSavePackageArgs SaveArgs;
|
|
||||||
SaveArgs.TopLevelFlags = RF_Standalone;
|
|
||||||
bool bSaved = UPackage::SavePackage(Package, NewStruct, *PackageFilename, SaveArgs);
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created UserDefinedStruct '%s' with %d properties, save %s"),
|
|
||||||
*AssetPath, PropsAdded, bSaved ? TEXT("succeeded") : TEXT("failed"));
|
|
||||||
|
|
||||||
Result->SetBoolField(TEXT("success"), true);
|
|
||||||
Result->SetStringField(TEXT("assetPath"), AssetPath);
|
|
||||||
Result->SetStringField(TEXT("assetName"), AssetName);
|
|
||||||
Result->SetNumberField(TEXT("propertiesAdded"), PropsAdded);
|
|
||||||
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// HandleCreateEnum — create a new UserDefinedEnum asset
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
void FBlueprintMCPServer::HandleCreateEnum(const FJsonObject* Json, FJsonObject* Result)
|
|
||||||
{
|
|
||||||
FString AssetPath = Json->GetStringField(TEXT("assetPath"));
|
|
||||||
if (AssetPath.IsEmpty())
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required field: assetPath (e.g. '/Game/DataTypes/E_MyEnum')"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Split path
|
|
||||||
FString PackagePath, AssetName;
|
|
||||||
int32 LastSlash;
|
|
||||||
if (AssetPath.FindLastChar('/', LastSlash))
|
|
||||||
{
|
|
||||||
PackagePath = AssetPath.Left(LastSlash);
|
|
||||||
AssetName = AssetPath.Mid(LastSlash + 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/E_MyEnum')"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (AssetName.IsEmpty())
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Invalid asset name in assetPath"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get values
|
|
||||||
const TArray<TSharedPtr<FJsonValue>>* ValuesArray = nullptr;
|
|
||||||
if (!Json->TryGetArrayField(TEXT("values"), ValuesArray) || !ValuesArray || ValuesArray->Num() == 0)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing or empty required field: values (array of strings)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
TArray<FString> EnumValues;
|
|
||||||
for (const TSharedPtr<FJsonValue>& Val : *ValuesArray)
|
|
||||||
{
|
|
||||||
FString Str = Val->AsString();
|
|
||||||
if (!Str.IsEmpty()) EnumValues.Add(Str);
|
|
||||||
}
|
|
||||||
if (EnumValues.Num() == 0)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("No valid enum values provided"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the enum using AssetTools
|
|
||||||
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
|
||||||
IAssetTools& AssetTools = AssetToolsModule.Get();
|
|
||||||
|
|
||||||
UEnumFactory* Factory = NewObject<UEnumFactory>();
|
|
||||||
UObject* NewAsset = AssetTools.CreateAsset(AssetName, PackagePath, UUserDefinedEnum::StaticClass(), Factory);
|
|
||||||
|
|
||||||
if (!NewAsset)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create UserDefinedEnum asset"));
|
|
||||||
}
|
|
||||||
|
|
||||||
UUserDefinedEnum* NewEnum = Cast<UUserDefinedEnum>(NewAsset);
|
|
||||||
if (!NewEnum)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Created asset is not a UserDefinedEnum"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add enum values — UUserDefinedEnum starts with a MAX value.
|
|
||||||
// We need to add entries before MAX.
|
|
||||||
for (int32 i = 0; i < EnumValues.Num(); ++i)
|
|
||||||
{
|
|
||||||
// AddNewEnumeratorForUserDefinedEnum adds before the _MAX entry (returns void)
|
|
||||||
FEnumEditorUtils::AddNewEnumeratorForUserDefinedEnum(NewEnum);
|
|
||||||
// The new entry is at index (NumEnums - 2) because _MAX is last
|
|
||||||
int32 NewIndex = NewEnum->NumEnums() - 2;
|
|
||||||
FEnumEditorUtils::SetEnumeratorDisplayName(NewEnum, NewIndex, FText::FromString(EnumValues[i]));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save
|
|
||||||
UPackage* Package = NewEnum->GetPackage();
|
|
||||||
FString PackageFilename = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension());
|
|
||||||
FSavePackageArgs SaveArgs;
|
|
||||||
SaveArgs.TopLevelFlags = RF_Standalone;
|
|
||||||
bool bSaved = UPackage::SavePackage(Package, NewEnum, *PackageFilename, SaveArgs);
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created UserDefinedEnum '%s' with %d values, save %s"),
|
|
||||||
*AssetPath, EnumValues.Num(), bSaved ? TEXT("succeeded") : TEXT("failed"));
|
|
||||||
|
|
||||||
Result->SetBoolField(TEXT("success"), true);
|
|
||||||
Result->SetStringField(TEXT("assetPath"), AssetPath);
|
|
||||||
Result->SetStringField(TEXT("assetName"), AssetName);
|
|
||||||
Result->SetNumberField(TEXT("valueCount"), EnumValues.Num());
|
|
||||||
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// HandleAddStructProperty — add a property to UserDefinedStruct
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
void FBlueprintMCPServer::HandleAddStructProperty(const FJsonObject* Json, FJsonObject* Result)
|
|
||||||
{
|
|
||||||
FString AssetPath = Json->GetStringField(TEXT("assetPath"));
|
|
||||||
FString PropName = Json->GetStringField(TEXT("name"));
|
|
||||||
FString PropType = Json->GetStringField(TEXT("type"));
|
|
||||||
|
|
||||||
if (AssetPath.IsEmpty() || PropName.IsEmpty() || PropType.IsEmpty())
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: assetPath, name, type"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the struct
|
|
||||||
UUserDefinedStruct* Struct = LoadObject<UUserDefinedStruct>(nullptr, *AssetPath);
|
|
||||||
if (!Struct)
|
|
||||||
{
|
|
||||||
// Try with asset name appended
|
|
||||||
FString FullPath = AssetPath + TEXT(".") + FPackageName::GetShortName(AssetPath);
|
|
||||||
Struct = LoadObject<UUserDefinedStruct>(nullptr, *FullPath);
|
|
||||||
}
|
|
||||||
if (!Struct)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve type
|
|
||||||
FEdGraphPinType PinType;
|
|
||||||
FString TypeError;
|
|
||||||
if (!MCPUtils::ResolveTypeFromString(PropType, PinType, TypeError))
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Cannot resolve type '%s': %s"), *PropType, *TypeError));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snapshot existing GUIDs so we can find the newly added one
|
|
||||||
TSet<FGuid> ExistingGuids;
|
|
||||||
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(Struct))
|
|
||||||
{
|
|
||||||
ExistingGuids.Add(Var.VarGuid);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bAdded = FStructureEditorUtils::AddVariable(Struct, PinType);
|
|
||||||
if (!bAdded)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to add property to struct"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the new variable by diffing GUID sets and rename it
|
|
||||||
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(Struct))
|
|
||||||
{
|
|
||||||
if (!ExistingGuids.Contains(Var.VarGuid))
|
|
||||||
{
|
|
||||||
FStructureEditorUtils::RenameVariable(Struct, Var.VarGuid, PropName);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save
|
|
||||||
UPackage* Package = Struct->GetPackage();
|
|
||||||
FString PackageFilename = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension());
|
|
||||||
FSavePackageArgs SaveArgs;
|
|
||||||
SaveArgs.TopLevelFlags = RF_Standalone;
|
|
||||||
bool bSaved = UPackage::SavePackage(Package, Struct, *PackageFilename, SaveArgs);
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Added property '%s' (%s) to struct '%s', save %s"),
|
|
||||||
*PropName, *PropType, *AssetPath, bSaved ? TEXT("succeeded") : TEXT("failed"));
|
|
||||||
|
|
||||||
Result->SetBoolField(TEXT("success"), true);
|
|
||||||
Result->SetStringField(TEXT("assetPath"), AssetPath);
|
|
||||||
Result->SetStringField(TEXT("propertyName"), PropName);
|
|
||||||
Result->SetStringField(TEXT("propertyType"), PropType);
|
|
||||||
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============================================================
|
|
||||||
// HandleRemoveStructProperty — remove a property from UserDefinedStruct
|
|
||||||
// ============================================================
|
|
||||||
|
|
||||||
void FBlueprintMCPServer::HandleRemoveStructProperty(const FJsonObject* Json, FJsonObject* Result)
|
|
||||||
{
|
|
||||||
FString AssetPath = Json->GetStringField(TEXT("assetPath"));
|
|
||||||
FString PropName = Json->GetStringField(TEXT("name"));
|
|
||||||
|
|
||||||
if (AssetPath.IsEmpty() || PropName.IsEmpty())
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, TEXT("Missing required fields: assetPath, name"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the struct
|
|
||||||
UUserDefinedStruct* Struct = LoadObject<UUserDefinedStruct>(nullptr, *AssetPath);
|
|
||||||
if (!Struct)
|
|
||||||
{
|
|
||||||
FString FullPath = AssetPath + TEXT(".") + FPackageName::GetShortName(AssetPath);
|
|
||||||
Struct = LoadObject<UUserDefinedStruct>(nullptr, *FullPath);
|
|
||||||
}
|
|
||||||
if (!Struct)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("UserDefinedStruct not found at '%s'"), *AssetPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find the property GUID by name
|
|
||||||
FGuid TargetGuid;
|
|
||||||
bool bFound = false;
|
|
||||||
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(Struct))
|
|
||||||
{
|
|
||||||
if (Var.FriendlyName == PropName || Var.VarName.ToString() == PropName)
|
|
||||||
{
|
|
||||||
TargetGuid = Var.VarGuid;
|
|
||||||
bFound = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!bFound)
|
|
||||||
{
|
|
||||||
// List available properties
|
|
||||||
TArray<TSharedPtr<FJsonValue>> AvailProps;
|
|
||||||
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(Struct))
|
|
||||||
{
|
|
||||||
AvailProps.Add(MakeShared<FJsonValueString>(Var.FriendlyName));
|
|
||||||
}
|
|
||||||
MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Property '%s' not found in struct '%s'"), *PropName, *AssetPath));
|
|
||||||
Result->SetArrayField(TEXT("availableProperties"), AvailProps);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool bRemoved = FStructureEditorUtils::RemoveVariable(Struct, TargetGuid);
|
|
||||||
if (!bRemoved)
|
|
||||||
{
|
|
||||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to remove property '%s'"), *PropName));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save
|
|
||||||
UPackage* Package = Struct->GetPackage();
|
|
||||||
FString PackageFilename = FPackageName::LongPackageNameToFilename(Package->GetName(), FPackageName::GetAssetPackageExtension());
|
|
||||||
FSavePackageArgs SaveArgs;
|
|
||||||
SaveArgs.TopLevelFlags = RF_Standalone;
|
|
||||||
bool bSaved = UPackage::SavePackage(Package, Struct, *PackageFilename, SaveArgs);
|
|
||||||
|
|
||||||
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Removed property '%s' from struct '%s', save %s"),
|
|
||||||
*PropName, *AssetPath, bSaved ? TEXT("succeeded") : TEXT("failed"));
|
|
||||||
|
|
||||||
Result->SetBoolField(TEXT("success"), true);
|
|
||||||
Result->SetStringField(TEXT("assetPath"), AssetPath);
|
|
||||||
Result->SetStringField(TEXT("removedProperty"), PropName);
|
|
||||||
Result->SetBoolField(TEXT("saved"), bSaved);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,361 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPAssetFinder.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
|
#include "StructUtils/UserDefinedStruct.h"
|
||||||
|
#include "Engine/UserDefinedEnum.h"
|
||||||
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
|
#include "UserDefinedStructure/UserDefinedStructEditorData.h"
|
||||||
|
#include "Kismet2/EnumEditorUtils.h"
|
||||||
|
#include "AssetRegistry/AssetRegistryModule.h"
|
||||||
|
#include "AssetRegistry/IAssetRegistry.h"
|
||||||
|
#include "AssetToolsModule.h"
|
||||||
|
#include "IAssetTools.h"
|
||||||
|
#include "Factories/StructureFactory.h"
|
||||||
|
#include "Factories/EnumFactory.h"
|
||||||
|
#include "MCPHandlers_UserTypes.generated.h"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS(meta=(ToolName="create_struct_asset"))
|
||||||
|
class UMCPHandler_CreateStruct : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Full package path for the new struct (e.g. '/Game/DataTypes/S_MyStruct')"))
|
||||||
|
FString AssetPath;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Optional, Description="Array of initial properties, each with 'name' and 'type' fields"))
|
||||||
|
FMCPJsonArray Properties;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Create a new UserDefinedStruct asset. "
|
||||||
|
"Optionally add initial properties via the 'properties' array (each element needs 'name' and 'type').");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
||||||
|
{
|
||||||
|
FString PackagePath, AssetName;
|
||||||
|
if (!MCPUtils::SplitAssetPath(AssetPath, PackagePath, AssetName))
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/S_MyStruct')"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if asset already exists
|
||||||
|
FAssetRegistryModule& ARM = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry");
|
||||||
|
FAssetData ExistingAsset = ARM.Get().GetAssetByObjectPath(FSoftObjectPath(AssetPath + TEXT(".") + AssetName));
|
||||||
|
if (ExistingAsset.IsValid())
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Asset already exists at '%s'"), *AssetPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the struct using the AssetTools factory
|
||||||
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||||||
|
IAssetTools& AssetTools = AssetToolsModule.Get();
|
||||||
|
|
||||||
|
UStructureFactory* Factory = NewObject<UStructureFactory>();
|
||||||
|
UObject* NewAsset = AssetTools.CreateAsset(AssetName, PackagePath, UUserDefinedStruct::StaticClass(), Factory);
|
||||||
|
|
||||||
|
if (!NewAsset)
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create UserDefinedStruct asset"));
|
||||||
|
}
|
||||||
|
|
||||||
|
UUserDefinedStruct* NewStruct = Cast<UUserDefinedStruct>(NewAsset);
|
||||||
|
if (!NewStruct)
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, TEXT("Created asset is not a UserDefinedStruct"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add properties if specified
|
||||||
|
int32 PropsAdded = 0;
|
||||||
|
for (const TSharedPtr<FJsonValue>& PropVal : Properties.Array)
|
||||||
|
{
|
||||||
|
TSharedPtr<FJsonObject> PropObj = PropVal->AsObject();
|
||||||
|
if (!PropObj) continue;
|
||||||
|
|
||||||
|
FString PropName = PropObj->GetStringField(TEXT("name"));
|
||||||
|
FString PropType = PropObj->GetStringField(TEXT("type"));
|
||||||
|
if (PropName.IsEmpty() || PropType.IsEmpty()) continue;
|
||||||
|
|
||||||
|
FEdGraphPinType PinType;
|
||||||
|
FString TypeError;
|
||||||
|
if (!MCPUtils::ResolveTypeFromString(PropType, PinType, TypeError))
|
||||||
|
{
|
||||||
|
UE_LOG(LogTemp, Warning, TEXT("BlueprintMCP: Could not resolve type '%s' for property '%s': %s"), *PropType, *PropName, *TypeError);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot existing GUIDs so we can find the newly added one
|
||||||
|
TSet<FGuid> ExistingGuids;
|
||||||
|
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(NewStruct))
|
||||||
|
{
|
||||||
|
ExistingGuids.Add(Var.VarGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bAdded = FStructureEditorUtils::AddVariable(NewStruct, PinType);
|
||||||
|
if (bAdded)
|
||||||
|
{
|
||||||
|
// Find the new variable by diffing GUID sets
|
||||||
|
FGuid NewPropGuid;
|
||||||
|
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(NewStruct))
|
||||||
|
{
|
||||||
|
if (!ExistingGuids.Contains(Var.VarGuid))
|
||||||
|
{
|
||||||
|
NewPropGuid = Var.VarGuid;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (NewPropGuid.IsValid())
|
||||||
|
{
|
||||||
|
FStructureEditorUtils::RenameVariable(NewStruct, NewPropGuid, PropName);
|
||||||
|
}
|
||||||
|
PropsAdded++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save
|
||||||
|
bool bSaved = MCPUtils::SaveGenericPackage(NewStruct);
|
||||||
|
|
||||||
|
Result->SetBoolField(TEXT("success"), true);
|
||||||
|
Result->SetStringField(TEXT("assetPath"), AssetPath);
|
||||||
|
Result->SetStringField(TEXT("assetName"), AssetName);
|
||||||
|
Result->SetNumberField(TEXT("propertiesAdded"), PropsAdded);
|
||||||
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS(meta=(ToolName="create_enum_asset"))
|
||||||
|
class UMCPHandler_CreateEnum : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Full package path for the new enum (e.g. '/Game/DataTypes/E_MyEnum')"))
|
||||||
|
FString AssetPath;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Array of enum value names"))
|
||||||
|
FMCPJsonArray Values;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Create a new UserDefinedEnum asset with the specified values.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
||||||
|
{
|
||||||
|
FString PackagePath, AssetName;
|
||||||
|
if (!MCPUtils::SplitAssetPath(AssetPath, PackagePath, AssetName))
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, TEXT("assetPath must be a full path (e.g. '/Game/DataTypes/E_MyEnum')"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<FString> EnumValues;
|
||||||
|
for (const TSharedPtr<FJsonValue>& Val : Values.Array)
|
||||||
|
{
|
||||||
|
FString Str = Val->AsString();
|
||||||
|
if (!Str.IsEmpty()) EnumValues.Add(Str);
|
||||||
|
}
|
||||||
|
if (EnumValues.Num() == 0)
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, TEXT("Missing or empty required field: values (array of strings)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the enum using AssetTools
|
||||||
|
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||||||
|
IAssetTools& AssetTools = AssetToolsModule.Get();
|
||||||
|
|
||||||
|
UEnumFactory* Factory = NewObject<UEnumFactory>();
|
||||||
|
UObject* NewAsset = AssetTools.CreateAsset(AssetName, PackagePath, UUserDefinedEnum::StaticClass(), Factory);
|
||||||
|
|
||||||
|
if (!NewAsset)
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to create UserDefinedEnum asset"));
|
||||||
|
}
|
||||||
|
|
||||||
|
UUserDefinedEnum* NewEnum = Cast<UUserDefinedEnum>(NewAsset);
|
||||||
|
if (!NewEnum)
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, TEXT("Created asset is not a UserDefinedEnum"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add enum values — UUserDefinedEnum starts with a MAX value.
|
||||||
|
// We need to add entries before MAX.
|
||||||
|
for (int32 i = 0; i < EnumValues.Num(); ++i)
|
||||||
|
{
|
||||||
|
// AddNewEnumeratorForUserDefinedEnum adds before the _MAX entry (returns void)
|
||||||
|
FEnumEditorUtils::AddNewEnumeratorForUserDefinedEnum(NewEnum);
|
||||||
|
// The new entry is at index (NumEnums - 2) because _MAX is last
|
||||||
|
int32 NewIndex = NewEnum->NumEnums() - 2;
|
||||||
|
FEnumEditorUtils::SetEnumeratorDisplayName(NewEnum, NewIndex, FText::FromString(EnumValues[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save
|
||||||
|
bool bSaved = MCPUtils::SaveGenericPackage(NewEnum);
|
||||||
|
|
||||||
|
Result->SetBoolField(TEXT("success"), true);
|
||||||
|
Result->SetStringField(TEXT("assetPath"), AssetPath);
|
||||||
|
Result->SetStringField(TEXT("assetName"), AssetName);
|
||||||
|
Result->SetNumberField(TEXT("valueCount"), EnumValues.Num());
|
||||||
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS(meta=(ToolName="add_struct_field"))
|
||||||
|
class UMCPHandler_AddStructField : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Name or package path of the struct asset"))
|
||||||
|
FString AssetPath;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Name for the new field"))
|
||||||
|
FString Name;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Type for the new field (e.g. 'int32', 'FString', 'FVector')"))
|
||||||
|
FString Type;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Add a new field to a UserDefinedStruct asset.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
||||||
|
{
|
||||||
|
// Find the struct
|
||||||
|
FString StructError;
|
||||||
|
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadStructByName(AssetPath, StructError);
|
||||||
|
if (!Struct)
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, StructError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve type
|
||||||
|
FEdGraphPinType PinType;
|
||||||
|
FString TypeError;
|
||||||
|
if (!MCPUtils::ResolveTypeFromString(Type, PinType, TypeError))
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Cannot resolve type '%s': %s"), *Type, *TypeError));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Snapshot existing GUIDs so we can find the newly added one
|
||||||
|
TSet<FGuid> ExistingGuids;
|
||||||
|
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(Struct))
|
||||||
|
{
|
||||||
|
ExistingGuids.Add(Var.VarGuid);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bAdded = FStructureEditorUtils::AddVariable(Struct, PinType);
|
||||||
|
if (!bAdded)
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, TEXT("Failed to add property to struct"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the new variable by diffing GUID sets and rename it
|
||||||
|
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(Struct))
|
||||||
|
{
|
||||||
|
if (!ExistingGuids.Contains(Var.VarGuid))
|
||||||
|
{
|
||||||
|
FStructureEditorUtils::RenameVariable(Struct, Var.VarGuid, Name);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save
|
||||||
|
bool bSaved = MCPUtils::SaveGenericPackage(Struct);
|
||||||
|
|
||||||
|
Result->SetBoolField(TEXT("success"), true);
|
||||||
|
Result->SetStringField(TEXT("assetPath"), AssetPath);
|
||||||
|
Result->SetStringField(TEXT("propertyName"), Name);
|
||||||
|
Result->SetStringField(TEXT("propertyType"), Type);
|
||||||
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS(meta=(ToolName="remove_struct_field"))
|
||||||
|
class UMCPHandler_RemoveStructField : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Name or package path of the struct asset"))
|
||||||
|
FString AssetPath;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Name of the field to remove"))
|
||||||
|
FString Name;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Remove a field from a UserDefinedStruct asset.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
||||||
|
{
|
||||||
|
// Find the struct
|
||||||
|
FString StructError;
|
||||||
|
UUserDefinedStruct* Struct = UMCPAssetFinder::LoadStructByName(AssetPath, StructError);
|
||||||
|
if (!Struct)
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, StructError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the property GUID by name
|
||||||
|
FGuid TargetGuid;
|
||||||
|
bool bFound = false;
|
||||||
|
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(Struct))
|
||||||
|
{
|
||||||
|
if (Var.FriendlyName == Name || Var.VarName.ToString() == Name)
|
||||||
|
{
|
||||||
|
TargetGuid = Var.VarGuid;
|
||||||
|
bFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!bFound)
|
||||||
|
{
|
||||||
|
// List available properties
|
||||||
|
TArray<TSharedPtr<FJsonValue>> AvailProps;
|
||||||
|
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(Struct))
|
||||||
|
{
|
||||||
|
AvailProps.Add(MakeShared<FJsonValueString>(Var.FriendlyName));
|
||||||
|
}
|
||||||
|
MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Property '%s' not found in struct '%s'"), *Name, *AssetPath));
|
||||||
|
Result->SetArrayField(TEXT("availableProperties"), AvailProps);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool bRemoved = FStructureEditorUtils::RemoveVariable(Struct, TargetGuid);
|
||||||
|
if (!bRemoved)
|
||||||
|
{
|
||||||
|
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Failed to remove property '%s'"), *Name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save
|
||||||
|
bool bSaved = MCPUtils::SaveGenericPackage(Struct);
|
||||||
|
|
||||||
|
Result->SetBoolField(TEXT("success"), true);
|
||||||
|
Result->SetStringField(TEXT("assetPath"), AssetPath);
|
||||||
|
Result->SetStringField(TEXT("removedProperty"), Name);
|
||||||
|
Result->SetBoolField(TEXT("saved"), bSaved);
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -862,10 +862,6 @@ void FMCPServer::RegisterHandlers()
|
|||||||
TEXT("add_blueprint_component"),
|
TEXT("add_blueprint_component"),
|
||||||
TEXT("remove_blueprint_component"),
|
TEXT("remove_blueprint_component"),
|
||||||
TEXT("restore_blueprint_graph_from_snapshot"),
|
TEXT("restore_blueprint_graph_from_snapshot"),
|
||||||
TEXT("create_struct_asset"),
|
|
||||||
TEXT("create_enum_asset"),
|
|
||||||
TEXT("add_struct_field"),
|
|
||||||
TEXT("remove_struct_field"),
|
|
||||||
TEXT("create_material_asset"),
|
TEXT("create_material_asset"),
|
||||||
TEXT("set_material_property"),
|
TEXT("set_material_property"),
|
||||||
TEXT("add_material_expression"),
|
TEXT("add_material_expression"),
|
||||||
@@ -929,10 +925,6 @@ void FMCPServer::RegisterHandlers()
|
|||||||
H(TEXT("restore_blueprint_graph_from_snapshot"), &FMCPServer::HandleRestoreGraph);
|
H(TEXT("restore_blueprint_graph_from_snapshot"), &FMCPServer::HandleRestoreGraph);
|
||||||
H(TEXT("find_pins_disconnected_since_snapshot"), &FMCPServer::HandleFindDisconnectedPins);
|
H(TEXT("find_pins_disconnected_since_snapshot"), &FMCPServer::HandleFindDisconnectedPins);
|
||||||
H(TEXT("analyze_cpp_rebuild_impact"), &FMCPServer::HandleAnalyzeRebuildImpact);
|
H(TEXT("analyze_cpp_rebuild_impact"), &FMCPServer::HandleAnalyzeRebuildImpact);
|
||||||
H(TEXT("create_struct_asset"), &FMCPServer::HandleCreateStruct);
|
|
||||||
H(TEXT("create_enum_asset"), &FMCPServer::HandleCreateEnum);
|
|
||||||
H(TEXT("add_struct_field"), &FMCPServer::HandleAddStructProperty);
|
|
||||||
H(TEXT("remove_struct_field"), &FMCPServer::HandleRemoveStructProperty);
|
|
||||||
H(TEXT("list_material_assets"), &FMCPServer::HandleListMaterials);
|
H(TEXT("list_material_assets"), &FMCPServer::HandleListMaterials);
|
||||||
H(TEXT("dump_material"), &FMCPServer::HandleGetMaterial);
|
H(TEXT("dump_material"), &FMCPServer::HandleGetMaterial);
|
||||||
H(TEXT("dump_material_expression_graph"), &FMCPServer::HandleGetMaterialGraph);
|
H(TEXT("dump_material_expression_graph"), &FMCPServer::HandleGetMaterialGraph);
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "Subsystems/EngineSubsystem.h"
|
#include "Subsystems/EngineSubsystem.h"
|
||||||
#include "AssetRegistry/AssetData.h"
|
#include "AssetRegistry/AssetData.h"
|
||||||
|
#include "StructUtils/UserDefinedStruct.h"
|
||||||
|
#include "Engine/UserDefinedEnum.h"
|
||||||
#include "MCPAssetFinder.generated.h"
|
#include "MCPAssetFinder.generated.h"
|
||||||
|
|
||||||
class UBlueprint;
|
class UBlueprint;
|
||||||
@@ -30,6 +32,8 @@ public:
|
|||||||
static const TArray<FAssetData>& GetMaterialAssets();
|
static const TArray<FAssetData>& GetMaterialAssets();
|
||||||
static const TArray<FAssetData>& GetMaterialInstanceAssets();
|
static const TArray<FAssetData>& GetMaterialInstanceAssets();
|
||||||
static const TArray<FAssetData>& GetMaterialFunctionAssets();
|
static const TArray<FAssetData>& GetMaterialFunctionAssets();
|
||||||
|
static const TArray<FAssetData>& GetStructAssets();
|
||||||
|
static const TArray<FAssetData>& GetEnumAssets();
|
||||||
|
|
||||||
// --- Static API: find/load helpers ---
|
// --- Static API: find/load helpers ---
|
||||||
// Find functions return nullptr if not found or if the short name is ambiguous.
|
// Find functions return nullptr if not found or if the short name is ambiguous.
|
||||||
@@ -46,6 +50,10 @@ public:
|
|||||||
static UMaterialInstanceConstant* LoadMaterialInstanceByName(const FString& NameOrPath, FString& OutError);
|
static UMaterialInstanceConstant* LoadMaterialInstanceByName(const FString& NameOrPath, FString& OutError);
|
||||||
static FAssetData* FindMaterialFunctionAsset(const FString& NameOrPath, FString* OutError = nullptr);
|
static FAssetData* FindMaterialFunctionAsset(const FString& NameOrPath, FString* OutError = nullptr);
|
||||||
static UMaterialFunction* LoadMaterialFunctionByName(const FString& NameOrPath, FString& OutError);
|
static UMaterialFunction* LoadMaterialFunctionByName(const FString& NameOrPath, FString& OutError);
|
||||||
|
static FAssetData* FindStructAsset(const FString& NameOrPath, FString* OutError = nullptr);
|
||||||
|
static UUserDefinedStruct* LoadStructByName(const FString& NameOrPath, FString& OutError);
|
||||||
|
static FAssetData* FindEnumAsset(const FString& NameOrPath, FString* OutError = nullptr);
|
||||||
|
static UUserDefinedEnum* LoadEnumByName(const FString& NameOrPath, FString& OutError);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/** Get the subsystem, refreshing asset caches if stale. Returns nullptr if engine is not initialized. */
|
/** Get the subsystem, refreshing asset caches if stale. Returns nullptr if engine is not initialized. */
|
||||||
@@ -58,6 +66,8 @@ private:
|
|||||||
TArray<FAssetData> AllMaterialAssets;
|
TArray<FAssetData> AllMaterialAssets;
|
||||||
TArray<FAssetData> AllMaterialInstanceAssets;
|
TArray<FAssetData> AllMaterialInstanceAssets;
|
||||||
TArray<FAssetData> AllMaterialFunctionAssets;
|
TArray<FAssetData> AllMaterialFunctionAssets;
|
||||||
|
TArray<FAssetData> AllStructAssets;
|
||||||
|
TArray<FAssetData> AllEnumAssets;
|
||||||
|
|
||||||
// Change detection — set true by asset registry delegates
|
// Change detection — set true by asset registry delegates
|
||||||
bool bDirty = true;
|
bool bDirty = true;
|
||||||
|
|||||||
@@ -135,12 +135,6 @@ private:
|
|||||||
void HandleCreateBlueprint(const FJsonObject* Json, FJsonObject* Result);
|
void HandleCreateBlueprint(const FJsonObject* Json, FJsonObject* Result);
|
||||||
void HandleCreateGraph(const FJsonObject* Json, FJsonObject* Result);
|
void HandleCreateGraph(const FJsonObject* Json, FJsonObject* Result);
|
||||||
|
|
||||||
// ----- User-defined types -----
|
|
||||||
void HandleCreateStruct(const FJsonObject* Json, FJsonObject* Result);
|
|
||||||
void HandleCreateEnum(const FJsonObject* Json, FJsonObject* Result);
|
|
||||||
void HandleAddStructProperty(const FJsonObject* Json, FJsonObject* Result);
|
|
||||||
void HandleRemoveStructProperty(const FJsonObject* Json, FJsonObject* Result);
|
|
||||||
|
|
||||||
// ----- Graph manipulation -----
|
// ----- Graph manipulation -----
|
||||||
void HandleDeleteGraph(const FJsonObject* Json, FJsonObject* Result);
|
void HandleDeleteGraph(const FJsonObject* Json, FJsonObject* Result);
|
||||||
void HandleRenameGraph(const FJsonObject* Json, FJsonObject* Result);
|
void HandleRenameGraph(const FJsonObject* Json, FJsonObject* Result);
|
||||||
|
|||||||
@@ -105,6 +105,21 @@ public:
|
|||||||
class MCPUtils
|
class MCPUtils
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// ----- Asset path helpers -----
|
||||||
|
// Splits "/Game/Foo/Bar" into PackagePath="/Game/Foo" and AssetName="Bar".
|
||||||
|
// Returns false if the path has no slash or the asset name is empty.
|
||||||
|
static bool SplitAssetPath(const FString& AssetPath, FString& OutPackagePath, FString& OutAssetName)
|
||||||
|
{
|
||||||
|
int32 LastSlash;
|
||||||
|
if (!AssetPath.FindLastChar('/', LastSlash))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
OutPackagePath = AssetPath.Left(LastSlash);
|
||||||
|
OutAssetName = AssetPath.Mid(LastSlash + 1);
|
||||||
|
return !OutAssetName.IsEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
// ----- JSON helpers -----
|
// ----- JSON helpers -----
|
||||||
static FString JsonToString(TSharedRef<FJsonObject> JsonObj);
|
static FString JsonToString(TSharedRef<FJsonObject> JsonObj);
|
||||||
static TSharedPtr<FJsonObject> ParseBodyJson(const FString& Body);
|
static TSharedPtr<FJsonObject> ParseBodyJson(const FString& Body);
|
||||||
|
|||||||
Reference in New Issue
Block a user