Files
integration/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_CreateStructAsset.h

141 lines
4.7 KiB
C
Raw Normal View History

2026-03-08 22:17:14 -04:00
#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 "UMCPHandler_CreateStructAsset.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
USTRUCT()
struct FStructPropertyEntry
{
GENERATED_BODY()
UPROPERTY()
FString Name;
UPROPERTY()
FString Type;
};
UCLASS()
class UMCPHandler_CreateStructAsset : 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)
{
FStructPropertyEntry Entry;
if (!MCPUtils::PopulateFromJson(FStructPropertyEntry::StaticStruct(), &Entry, PropVal, Result)) return;
if (Entry.Name.IsEmpty() || Entry.Type.IsEmpty()) continue;
FEdGraphPinType PinType;
FString TypeError;
if (!MCPUtils::ResolveTypeFromString(Entry.Type, PinType, TypeError))
{
UE_LOG(LogTemp, Warning, TEXT("BlueprintMCP: Could not resolve type '%s' for property '%s': %s"), *Entry.Type, *Entry.Name, *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, Entry.Name);
}
PropsAdded++;
}
}
// Save
bool bSaved = MCPUtils::SaveGenericPackage(NewStruct);
Result->SetStringField(TEXT("assetName"), AssetName);
Result->SetNumberField(TEXT("propertiesAdded"), PropsAdded);
Result->SetBoolField(TEXT("saved"), bSaved);
}
};