diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/AnimBlueprint_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/AnimBlueprint_Create.h index ae5b439c..55066ddc 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/AnimBlueprint_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/AnimBlueprint_Create.h @@ -4,6 +4,7 @@ #include "MCPHandler.h" #include "MCPAssetFinder.h" #include "MCPUtils.h" +#include "MCPPackageMaker.h" #include "Animation/AnimBlueprint.h" #include "Animation/AnimBlueprintGeneratedClass.h" #include "Animation/AnimInstance.h" @@ -22,11 +23,8 @@ class UMCP_AnimBlueprint_Create : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Name for the new Animation Blueprint asset")) - FString Name; - - UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)")) - FString PackagePath; + UPROPERTY(meta=(Description="Full asset path for the new Animation Blueprint (e.g. '/Game/AnimBP/ABP_Character')")) + FString AssetPath; UPROPERTY(meta=(Description="Name or path of the skeleton asset to use")) FString Skeleton; @@ -43,19 +41,8 @@ public: { MCPErrorCallback CB(Result); - if (Name.IsEmpty() || PackagePath.IsEmpty() || Skeleton.IsEmpty()) - { - return CB.SetError(TEXT("Missing required fields: name, packagePath, skeleton")); - } - - if (!PackagePath.StartsWith(TEXT("/Game"))) - { - return CB.SetError(TEXT("packagePath must start with '/Game'")); - } - - // Check if asset already exists - MCPAssets ExistCheck; - if (!ExistCheck.Exact(Name).Errors(CB).EAny().Info()) return; + MCPPackageMaker Maker(AssetPath, CB); + if (!Maker.Ok()) return; // Resolve skeleton MCPAssets SkeletonAssets; @@ -82,20 +69,14 @@ public: ParentClassObj = Found; } - // Create the package - FString FullPackagePath = PackagePath / Name; - UPackage* Package = CreatePackage(*FullPackagePath); - if (!Package) - { - return CB.SetError(FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath)); - } + // Create the package and Animation Blueprint + if (!Maker.Make()) return; - // Create the Animation Blueprint UAnimBlueprint* NewAnimBP = CastChecked( FKismetEditorUtilities::CreateBlueprint( ParentClassObj, - Package, - FName(*Name), + Maker.Package(), + FName(*Maker.Name()), BPTYPE_Normal, UAnimBlueprint::StaticClass(), UAnimBlueprintGeneratedClass::StaticClass() @@ -113,7 +94,7 @@ public: FKismetEditorUtilities::CompileBlueprint(NewAnimBP); bool bSaved = MCPUtils::SaveBlueprintPackage(NewAnimBP); - Result.Appendf(TEXT("Created: %s\n"), *FullPackagePath); + Result.Appendf(TEXT("Created: %s\n"), *AssetPath); Result.Appendf(TEXT("ParentClass: %s\n"), *MCPUtils::FormatName(ParentClassObj)); Result.Appendf(TEXT("Saved: %s\n"), bSaved ? TEXT("true") : TEXT("false")); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlendSpace_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlendSpace_Create.h index 94df63eb..90a79543 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlendSpace_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlendSpace_Create.h @@ -4,6 +4,7 @@ #include "MCPHandler.h" #include "MCPAssetFinder.h" #include "MCPUtils.h" +#include "MCPPackageMaker.h" #include "Animation/Skeleton.h" #include "Animation/BlendSpace.h" #include "BlendSpace_Create.generated.h" @@ -19,11 +20,8 @@ class UMCP_BlendSpace_Create : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Name for the new Blend Space asset")) - FString Name; - - UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)")) - FString PackagePath; + UPROPERTY(meta=(Description="Full asset path for the new Blend Space (e.g. '/Game/BlendSpaces/BS_Locomotion')")) + FString AssetPath; UPROPERTY(meta=(Description="Name or path of the skeleton asset to use")) FString Skeleton; @@ -35,32 +33,19 @@ public: virtual void Handle(FStringBuilderBase& Result) override { - if (!PackagePath.StartsWith(TEXT("/Game"))) - { - Result.Append(TEXT("ERROR: PackagePath must start with '/Game'\n")); - return; - } + MCPErrorCallback CB(Result); - // Check if an asset with this name already exists. - MCPAssets ExistCheck; - if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; + MCPPackageMaker Maker(AssetPath, CB); + if (!Maker.Ok()) return; // Resolve skeleton. MCPAssets SkeletonAssets; - if (!SkeletonAssets.Exact(Skeleton).Errors(Result).ENone().ETwo().Load()) return; + if (!SkeletonAssets.Exact(Skeleton).Errors(CB).ENone().ETwo().Load()) return; USkeleton* SkeletonObj = SkeletonAssets.Object(); - // Create the package. - FString FullPackagePath = PackagePath / Name; - UPackage* Package = CreatePackage(*FullPackagePath); - if (!Package) - { - Result.Appendf(TEXT("ERROR: Failed to create package at '%s'\n"), *FullPackagePath); - return; - } - - // Create the Blend Space. - UBlendSpace* NewBS = NewObject(Package, FName(*Name), RF_Public | RF_Standalone); + // Create the package and Blend Space. + if (!Maker.Make()) return; + UBlendSpace* NewBS = NewObject(Maker.Package(), FName(*Maker.Name()), RF_Public | RF_Standalone); if (!NewBS) { Result.Append(TEXT("ERROR: Failed to create Blend Space object\n")); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Create.h index ecabe1d4..b774ff6f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Create.h @@ -2,8 +2,8 @@ #include "CoreMinimal.h" #include "MCPHandler.h" -#include "MCPAssetFinder.h" #include "MCPUtils.h" +#include "MCPPackageMaker.h" #include "Engine/Blueprint.h" #include "Kismet2/KismetEditorUtilities.h" #include "Blueprint_Create.generated.h" @@ -19,11 +19,8 @@ class UMCP_Blueprint_Create : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="New Blueprint asset name")) - FString Blueprint; - - UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)")) - FString PackagePath; + UPROPERTY(meta=(Description="Full asset path for the new Blueprint (e.g. '/Game/Blueprints/BP_MyActor')")) + FString AssetPath; UPROPERTY(meta=(Description="Parent class name (C++ class name or Blueprint name)")) FString ParentClass; @@ -40,15 +37,8 @@ public: { MCPErrorCallback Error(Result); - // Validate packagePath starts with /Game - if (!PackagePath.StartsWith(TEXT("/Game"))) - { - return Error.SetError(TEXT("PackagePath must start with '/Game'")); - } - - // Check if asset already exists - MCPAssets ExistCheck; - if (!ExistCheck.Exact(Blueprint).Errors(Error).EAny().Info()) return; + MCPPackageMaker Maker(AssetPath, Error); + if (!Maker.Ok()) return; // Resolve parent class — try C++ class first, then Blueprint asset UClass* ParentClassObj = MCPUtils::FindClassByName(ParentClass); @@ -81,19 +71,13 @@ public: ParentClassObj = UInterface::StaticClass(); } - // Create the package - FString FullPackagePath = PackagePath / Blueprint; - UPackage* Package = CreatePackage(*FullPackagePath); - if (!Package) - { - return Error.SetError(FString::Printf(TEXT("Failed to create package at '%s'"), *FullPackagePath)); - } + // Create the package and Blueprint + if (!Maker.Make()) return; - // Create the Blueprint UBlueprint* NewBP = FKismetEditorUtilities::CreateBlueprint( ParentClassObj, - Package, - FName(*Blueprint), + Maker.Package(), + FName(*Maker.Name()), BlueprintTypeEnum, UBlueprint::StaticClass(), UBlueprintGeneratedClass::StaticClass() diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Enum_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Enum_Create.h index 4db2259a..3d6eb04f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Enum_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Enum_Create.h @@ -6,9 +6,8 @@ #include "MCPUtils.h" #include "Engine/UserDefinedEnum.h" #include "Kismet2/EnumEditorUtils.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" #include "Factories/EnumFactory.h" +#include "MCPPackageMaker.h" #include "Enum_Create.generated.h" @@ -35,12 +34,10 @@ public: virtual void Handle(FStringBuilderBase& Result) override { - FString PackagePath, AssetName; - if (!MCPUtils::SplitAssetPath(AssetPath, PackagePath, AssetName)) - { - Result.Append(TEXT("ERROR: AssetPath must be a full path (e.g. '/Game/DataTypes/E_MyEnum')\n")); - return; - } + MCPErrorCallback Error(Result); + + MCPPackageMaker Maker(AssetPath, Error); + if (!Maker.Ok()) return; TArray EnumValues; for (const TSharedPtr& Val : Values.Array) @@ -50,25 +47,12 @@ public: } if (EnumValues.Num() == 0) { - Result.Append(TEXT("ERROR: Values must be a non-empty array of strings\n")); - return; + return Error.SetError(TEXT("Values must be a non-empty array of strings")); } - // Check that no enum with this name already exists. - MCPAssets ExistCheck; - if (!ExistCheck.Exact(AssetName).Errors(Result).EAny().Info()) return; - // Create the enum using AssetTools. - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - UEnumFactory* Factory = NewObject(); - UObject* NewAsset = AssetTools.CreateAsset(AssetName, PackagePath, UUserDefinedEnum::StaticClass(), Factory); - - UUserDefinedEnum* NewEnum = Cast(NewAsset); - if (!NewEnum) - { - Result.Append(TEXT("ERROR: Failed to create UserDefinedEnum asset\n")); - return; - } + UUserDefinedEnum* NewEnum = Maker.CreateAsset(); + if (!NewEnum) return; // Add enum values — UUserDefinedEnum starts with a MAX value. // We need to add entries before MAX. diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialFunction_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialFunction_Create.h index a2e15937..6f8c9af9 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialFunction_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialFunction_Create.h @@ -6,8 +6,7 @@ #include "MCPUtils.h" #include "Materials/MaterialFunction.h" #include "Factories/MaterialFunctionFactoryNew.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" +#include "MCPPackageMaker.h" #include "MaterialFunction_Create.generated.h" @@ -21,11 +20,8 @@ class UMCP_MaterialFunction_Create : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Name for the new material function asset")) - FString Name; - - UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)")) - FString PackagePath; + UPROPERTY(meta=(Description="Full asset path for the new material function (e.g. '/Game/Materials/MF_MyFunc')")) + FString AssetPath; UPROPERTY(meta=(Optional, Description="Description for the material function")) FString Description; @@ -37,27 +33,14 @@ public: virtual void Handle(FStringBuilderBase& Result) override { - if (!PackagePath.StartsWith(TEXT("/Game"))) - { - Result.Append(TEXT("ERROR: PackagePath must start with '/Game'\n")); - return; - } + MCPErrorCallback Error(Result); - // Check if asset already exists. - MCPAssets ExistCheck; - if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; + MCPPackageMaker Maker(AssetPath, Error); + if (!Maker.Ok()) return; // Create via IAssetTools + factory. - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - UMaterialFunctionFactoryNew* Factory = NewObject(); - UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterialFunction::StaticClass(), Factory); - - UMaterialFunction* MF = Cast(NewAsset); - if (!MF) - { - Result.Appendf(TEXT("ERROR: Failed to create Material Function '%s' in '%s'\n"), *Name, *PackagePath); - return; - } + UMaterialFunction* MF = Maker.CreateAsset(); + if (!MF) return; // Set optional description. if (!Description.IsEmpty()) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_Create.h index 880229b7..11faf1db 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_Create.h @@ -8,8 +8,7 @@ #include "Materials/MaterialInterface.h" #include "Materials/MaterialInstanceConstant.h" #include "Factories/MaterialInstanceConstantFactoryNew.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" +#include "MCPPackageMaker.h" #include "MaterialInstance_Create.generated.h" @@ -23,11 +22,8 @@ class UMCP_MaterialInstance_Create : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Name for the new Material Instance asset")) - FString Name; - - UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)")) - FString PackagePath; + UPROPERTY(meta=(Description="Full asset path for the new Material Instance (e.g. '/Game/Materials/MI_GoldShiny')")) + FString AssetPath; UPROPERTY(meta=(Description="Parent material name or path (Material or Material Instance)")) FString ParentMaterial; @@ -39,15 +35,10 @@ public: virtual void Handle(FStringBuilderBase& Result) override { - if (!PackagePath.StartsWith(TEXT("/Game"))) - { - Result.Append(TEXT("ERROR: PackagePath must start with '/Game'\n")); - return; - } + MCPErrorCallback Error(Result); - // Check if asset already exists. - MCPAssets ExistCheck; - if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; + MCPPackageMaker Maker(AssetPath, Error); + if (!Maker.Ok()) return; // Load parent material -- try as Material first, then as Material Instance. UMaterialInterface* ParentMaterialObj = nullptr; @@ -79,16 +70,8 @@ public: } // Create via factory + AssetTools. - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - UMaterialInstanceConstantFactoryNew* Factory = NewObject(); - - UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterialInstanceConstant::StaticClass(), Factory); - UMaterialInstanceConstant* MI = Cast(NewAsset); - if (!MI) - { - Result.Appendf(TEXT("ERROR: Failed to create Material Instance '%s' in '%s'\n"), *Name, *PackagePath); - return; - } + UMaterialInstanceConstant* MI = Maker.CreateAsset(); + if (!MI) return; // Set parent. TArray Chain = { MI }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h index 46080991..e3d046f3 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h @@ -7,8 +7,7 @@ #include "Materials/Material.h" #include "MaterialDomain.h" #include "Factories/MaterialFactoryNew.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" +#include "MCPPackageMaker.h" #include "Material_Create.generated.h" @@ -22,11 +21,8 @@ class UMCP_Material_Create : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Name for the new material asset")) - FString Name; - - UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)")) - FString PackagePath; + UPROPERTY(meta=(Description="Full asset path for the new material (e.g. '/Game/Materials/M_Gold')")) + FString AssetPath; UPROPERTY(meta=(Optional, Description="Material domain: Surface, DeferredDecal, LightFunction, Volume, PostProcess, UI")) FString Domain; @@ -44,42 +40,29 @@ public: virtual void Handle(FStringBuilderBase& Result) override { - if (!PackagePath.StartsWith(TEXT("/Game"))) - { - Result.Append(TEXT("ERROR: PackagePath must start with '/Game'\n")); - return; - } + MCPErrorCallback Error(Result); + + MCPPackageMaker Maker(AssetPath, Error); + if (!Maker.Ok()) return; // Parse optional enum properties before creating the asset. EMaterialDomain ParsedDomain = MD_Surface; if (!Domain.IsEmpty()) { - if (!MCPUtils::StringToEnum(Domain, ParsedDomain, Result, TEXT("MD_"))) + if (!MCPUtils::StringToEnum(Domain, ParsedDomain, Error, TEXT("MD_"))) return; } EBlendMode ParsedBlendMode = BLEND_Opaque; if (!BlendMode.IsEmpty()) { - if (!MCPUtils::StringToEnum(BlendMode, ParsedBlendMode, Result, TEXT("BLEND_"))) + if (!MCPUtils::StringToEnum(BlendMode, ParsedBlendMode, Error, TEXT("BLEND_"))) return; } - // Check if an asset with this name already exists. - MCPAssets ExistCheck; - if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; - // Create via IAssetTools + factory. - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - UMaterialFactoryNew* Factory = NewObject(); - UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterial::StaticClass(), Factory); - - UMaterial* MaterialObj = Cast(NewAsset); - if (!MaterialObj) - { - Result.Appendf(TEXT("ERROR: Failed to create Material '%s' in '%s'\n"), *Name, *PackagePath); - return; - } + UMaterial* MaterialObj = Maker.CreateAsset(); + if (!MaterialObj) return; // Apply optional properties. TArray Chain = { MaterialObj }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Property_Dump.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Property_Dump.h index efb156fa..46da8ba7 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Property_Dump.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Property_Dump.h @@ -44,30 +44,45 @@ public: TArray Props = MCPUtils::SearchProperties(Template, Query, CPF_Edit, Local); - UStruct* CurrentOwner = nullptr; + // Group properties by category. + TMap> ByCategory; for (FProperty* Prop : Props) { - FString PropName = MCPUtils::FormatName(Prop); + FString Category = Prop->HasMetaData(TEXT("Category")) ? Prop->GetMetaData(TEXT("Category")) : FString(); + ByCategory.FindOrAdd(Category).Add(Prop); + } - // Print section heading when the owning class changes. - UStruct* Owner = Prop->GetOwnerStruct(); - if (Owner != CurrentOwner) + // Sort category names, putting empty category last. + TArray Categories; + ByCategory.GetKeys(Categories); + Categories.Sort([](const FString& A, const FString& B) { + if (A.IsEmpty()) return false; + if (B.IsEmpty()) return true; + return A < B; + }); + + for (const FString& Category : Categories) + { + if (Category.IsEmpty()) + Result.Append(TEXT("\nUncategorized:\n")); + else + Result.Appendf(TEXT("\n%s:\n"), *Category); + + for (FProperty* Prop : ByCategory[Category]) { - CurrentOwner = Owner; - Result.Appendf(TEXT("\nFrom %s:\n\n"), *MCPUtils::FormatName(Owner)); + FString PropName = MCPUtils::FormatName(Prop); + FString ValueStr = MCPUtils::GetPropertyValueText(Template, Prop); + + if (Truncate && (ValueStr.Len() > 80)) + ValueStr = ValueStr.Left(80) + TEXT("..."); + + bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst); + Result.Appendf(TEXT(" %s %s %s = %s\n"), + bEditable ? TEXT("editable") : TEXT("readonly"), + *MCPUtils::FormatPropertyType(Prop), + *PropName, + *ValueStr); } - - FString ValueStr = MCPUtils::GetPropertyValueText(Template, Prop); - - if (Truncate && (ValueStr.Len() > 80)) - ValueStr = ValueStr.Left(80) + TEXT("..."); - - bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst); - Result.Appendf(TEXT(" %s %s %s = %s\n"), - bEditable ? TEXT("editable") : TEXT("readonly"), - *MCPUtils::FormatPropertyType(Prop), - *PropName, - *ValueStr); } if (Props.IsEmpty()) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Struct_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Struct_Create.h index 3fb194d7..6f18ddad 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Struct_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Struct_Create.h @@ -7,9 +7,8 @@ #include "StructUtils/UserDefinedStruct.h" #include "Kismet2/BlueprintEditorUtils.h" #include "UserDefinedStructure/UserDefinedStructEditorData.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" #include "Factories/StructureFactory.h" +#include "MCPPackageMaker.h" #include "Struct_Create.generated.h" @@ -35,11 +34,8 @@ class UMCP_Struct_Create : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Name for the new struct asset")) - FString Name; - - UPROPERTY(meta=(Description="Package path where the asset will be created (must start with /Game)")) - FString PackagePath; + UPROPERTY(meta=(Description="Full asset 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; @@ -51,27 +47,14 @@ public: virtual void Handle(FStringBuilderBase& Result) override { - if (!PackagePath.StartsWith(TEXT("/Game"))) - { - Result.Append(TEXT("ERROR: PackagePath must start with '/Game'\n")); - return; - } + MCPErrorCallback Error(Result); - // Check if an asset with this name already exists. - MCPAssets ExistCheck; - if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return; + MCPPackageMaker Maker(AssetPath, Error); + if (!Maker.Ok()) return; // Create the struct using the AssetTools factory. - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - UStructureFactory* Factory = NewObject(); - UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UUserDefinedStruct::StaticClass(), Factory); - - UUserDefinedStruct* NewStruct = Cast(NewAsset); - if (!NewStruct) - { - Result.Appendf(TEXT("ERROR: Failed to create struct '%s' in '%s'\n"), *Name, *PackagePath); - return; - } + UUserDefinedStruct* NewStruct = Maker.CreateAsset(); + if (!NewStruct) return; // Add properties if specified. int32 PropsAdded = 0; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPPackageMaker.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPPackageMaker.h new file mode 100644 index 00000000..eb27a987 --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPPackageMaker.h @@ -0,0 +1,77 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPUtils.h" +#include "UObject/Package.h" +#include "AssetToolsModule.h" +#include "IAssetTools.h" + +// Helper for creating new asset packages. Validates the path and checks for +// conflicting assets in the constructor. Call Ok() to check, then Make() to +// actually create the UPackage. +class MCPPackageMaker +{ +public: + MCPPackageMaker(const FString& InFullPath, MCPErrorCallback InError) + : FullPath(InFullPath) + , Error(InError) + { + // Path must start with /Game. + if (!FullPath.StartsWith(TEXT("/Game"))) + { + Error.SetError(FString::Printf(TEXT("Package path '%s' must start with '/Game'"), *FullPath)); + bError = true; + return; + } + + // Check for an existing asset at this path. + if (FindObject(nullptr, *FullPath)) + { + Error.SetError(FString::Printf(TEXT("An asset already exists at '%s'"), *FullPath)); + bError = true; + return; + } + } + + bool Ok() const { return !bError; } + + bool Make() + { + if (bError) return false; + Pkg = CreatePackage(*FullPath); + if (!Pkg) + { + Error.SetError(FString::Printf(TEXT("Failed to create package at '%s'"), *FullPath)); + bError = true; + return false; + } + return true; + } + + UPackage* Package() const { return Pkg; } + FString Name() const { return FPackageName::GetShortName(FullPath); } + + template + AssetClass* CreateAsset() + { + if (bError) return nullptr; + IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); + FactoryClass* Factory = NewObject(); + FString PkgPath = FPackageName::GetLongPackagePath(FullPath); + FString AssetName = FPackageName::GetShortName(FullPath); + UObject* NewAsset = AssetTools.CreateAsset(AssetName, PkgPath, AssetClass::StaticClass(), Factory); + AssetClass* Result = Cast(NewAsset); + if (!Result) + { + Error.SetError(FString::Printf(TEXT("Failed to create asset at '%s'"), *FullPath)); + bError = true; + } + return Result; + } + +private: + FString FullPath; + MCPErrorCallback Error; + UPackage* Pkg = nullptr; + bool bError = false; +};