Overhaul asset creation handlers.

This commit is contained in:
2026-03-12 18:31:55 -04:00
parent 4c6bdae2c2
commit c6f7a89ccd
10 changed files with 183 additions and 225 deletions

View File

@@ -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<UBlueprint> ExistCheck;
if (!ExistCheck.Exact(Name).Errors(CB).EAny().Info()) return;
MCPPackageMaker Maker(AssetPath, CB);
if (!Maker.Ok()) return;
// Resolve skeleton
MCPAssets<USkeleton> 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<UAnimBlueprint>(
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"));

View File

@@ -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<UBlendSpace> ExistCheck;
if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return;
MCPPackageMaker Maker(AssetPath, CB);
if (!Maker.Ok()) return;
// Resolve skeleton.
MCPAssets<USkeleton> 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<UBlendSpace>(Package, FName(*Name), RF_Public | RF_Standalone);
// Create the package and Blend Space.
if (!Maker.Make()) return;
UBlendSpace* NewBS = NewObject<UBlendSpace>(Maker.Package(), FName(*Maker.Name()), RF_Public | RF_Standalone);
if (!NewBS)
{
Result.Append(TEXT("ERROR: Failed to create Blend Space object\n"));

View File

@@ -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<UBlueprint> 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()

View File

@@ -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<FString> EnumValues;
for (const TSharedPtr<FJsonValue>& 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<UUserDefinedEnum> ExistCheck;
if (!ExistCheck.Exact(AssetName).Errors(Result).EAny().Info()) return;
// Create the enum using AssetTools.
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
UEnumFactory* Factory = NewObject<UEnumFactory>();
UObject* NewAsset = AssetTools.CreateAsset(AssetName, PackagePath, UUserDefinedEnum::StaticClass(), Factory);
UUserDefinedEnum* NewEnum = Cast<UUserDefinedEnum>(NewAsset);
if (!NewEnum)
{
Result.Append(TEXT("ERROR: Failed to create UserDefinedEnum asset\n"));
return;
}
UUserDefinedEnum* NewEnum = Maker.CreateAsset<UUserDefinedEnum, UEnumFactory>();
if (!NewEnum) return;
// Add enum values — UUserDefinedEnum starts with a MAX value.
// We need to add entries before MAX.

View File

@@ -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<UMaterialFunction> 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<FAssetToolsModule>("AssetTools").Get();
UMaterialFunctionFactoryNew* Factory = NewObject<UMaterialFunctionFactoryNew>();
UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterialFunction::StaticClass(), Factory);
UMaterialFunction* MF = Cast<UMaterialFunction>(NewAsset);
if (!MF)
{
Result.Appendf(TEXT("ERROR: Failed to create Material Function '%s' in '%s'\n"), *Name, *PackagePath);
return;
}
UMaterialFunction* MF = Maker.CreateAsset<UMaterialFunction, UMaterialFunctionFactoryNew>();
if (!MF) return;
// Set optional description.
if (!Description.IsEmpty())

View File

@@ -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<UMaterialInstanceConstant> 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<FAssetToolsModule>("AssetTools").Get();
UMaterialInstanceConstantFactoryNew* Factory = NewObject<UMaterialInstanceConstantFactoryNew>();
UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterialInstanceConstant::StaticClass(), Factory);
UMaterialInstanceConstant* MI = Cast<UMaterialInstanceConstant>(NewAsset);
if (!MI)
{
Result.Appendf(TEXT("ERROR: Failed to create Material Instance '%s' in '%s'\n"), *Name, *PackagePath);
return;
}
UMaterialInstanceConstant* MI = Maker.CreateAsset<UMaterialInstanceConstant, UMaterialInstanceConstantFactoryNew>();
if (!MI) return;
// Set parent.
TArray<UObject*> Chain = { MI };

View File

@@ -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<UMaterial> ExistCheck;
if (!ExistCheck.Exact(Name).Errors(Result).EAny().Info()) return;
// Create via IAssetTools + factory.
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
UMaterialFactoryNew* Factory = NewObject<UMaterialFactoryNew>();
UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UMaterial::StaticClass(), Factory);
UMaterial* MaterialObj = Cast<UMaterial>(NewAsset);
if (!MaterialObj)
{
Result.Appendf(TEXT("ERROR: Failed to create Material '%s' in '%s'\n"), *Name, *PackagePath);
return;
}
UMaterial* MaterialObj = Maker.CreateAsset<UMaterial, UMaterialFactoryNew>();
if (!MaterialObj) return;
// Apply optional properties.
TArray<UObject*> Chain = { MaterialObj };

View File

@@ -44,30 +44,45 @@ public:
TArray<FProperty*> Props = MCPUtils::SearchProperties(Template, Query, CPF_Edit, Local);
UStruct* CurrentOwner = nullptr;
// Group properties by category.
TMap<FString, TArray<FProperty*>> 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<FString> 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())

View File

@@ -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<UUserDefinedStruct> 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<FAssetToolsModule>("AssetTools").Get();
UStructureFactory* Factory = NewObject<UStructureFactory>();
UObject* NewAsset = AssetTools.CreateAsset(Name, PackagePath, UUserDefinedStruct::StaticClass(), Factory);
UUserDefinedStruct* NewStruct = Cast<UUserDefinedStruct>(NewAsset);
if (!NewStruct)
{
Result.Appendf(TEXT("ERROR: Failed to create struct '%s' in '%s'\n"), *Name, *PackagePath);
return;
}
UUserDefinedStruct* NewStruct = Maker.CreateAsset<UUserDefinedStruct, UStructureFactory>();
if (!NewStruct) return;
// Add properties if specified.
int32 PropsAdded = 0;

View File

@@ -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<UPackage>(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<typename AssetClass, typename FactoryClass>
AssetClass* CreateAsset()
{
if (bError) return nullptr;
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
FactoryClass* Factory = NewObject<FactoryClass>();
FString PkgPath = FPackageName::GetLongPackagePath(FullPath);
FString AssetName = FPackageName::GetShortName(FullPath);
UObject* NewAsset = AssetTools.CreateAsset(AssetName, PkgPath, AssetClass::StaticClass(), Factory);
AssetClass* Result = Cast<AssetClass>(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;
};