More MCP refactoring

This commit is contained in:
2026-03-08 05:00:07 -04:00
parent d9072cf551
commit 93d4ed2038
7 changed files with 70 additions and 174 deletions

View File

@@ -91,7 +91,9 @@ public:
*Blueprint, *OldParentName, *NewParentClassObj->GetName());
// Perform reparent
BP->PreEditChange(nullptr);
BP->ParentClass = NewParentClassObj;
BP->PostEditChange();
// Refresh all nodes to pick up new parent's functions/variables
FBlueprintEditorUtils::RefreshAllNodes(BP);
@@ -187,24 +189,7 @@ public:
EBlueprintType BlueprintTypeEnum = BPTYPE_Normal;
if (!BlueprintType.IsEmpty())
{
if (BlueprintType == TEXT("Interface"))
{
BlueprintTypeEnum = BPTYPE_Interface;
}
else if (BlueprintType == TEXT("FunctionLibrary"))
{
BlueprintTypeEnum = BPTYPE_FunctionLibrary;
}
else if (BlueprintType == TEXT("MacroLibrary"))
{
BlueprintTypeEnum = BPTYPE_MacroLibrary;
}
else if (BlueprintType != TEXT("Normal"))
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Invalid blueprintType '%s'. Valid values: Normal, Interface, FunctionLibrary, MacroLibrary"),
*BlueprintType));
}
if (!MCPUtils::StringToEnum(BlueprintType, BlueprintTypeEnum, Result, TEXT("BPTYPE_"))) return;
}
// For Interface type, parent must be UInterface

View File

@@ -161,40 +161,13 @@ public:
bool bSaved = MCPUtils::SaveMaterialPackage(MaterialObj);
// Map domain back to string for response
auto DomainToString = [](EMaterialDomain InDomain) -> FString
{
switch (InDomain)
{
case MD_Surface: return TEXT("Surface");
case MD_DeferredDecal: return TEXT("DeferredDecal");
case MD_LightFunction: return TEXT("LightFunction");
case MD_Volume: return TEXT("Volume");
case MD_PostProcess: return TEXT("PostProcess");
case MD_UI: return TEXT("UI");
default: return TEXT("Surface");
}
};
auto BlendModeToString = [](EBlendMode Mode) -> FString
{
switch (Mode)
{
case BLEND_Opaque: return TEXT("Opaque");
case BLEND_Masked: return TEXT("Masked");
case BLEND_Translucent: return TEXT("Translucent");
case BLEND_Additive: return TEXT("Additive");
case BLEND_Modulate: return TEXT("Modulate");
default: return TEXT("Opaque");
}
};
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: Created Material '%s' (saved: %s)"),
*Name, bSaved ? TEXT("true") : TEXT("false"));
Result->SetStringField(TEXT("path"), MaterialObj->GetPathName());
Result->SetStringField(TEXT("domain"), DomainToString(MaterialObj->MaterialDomain));
Result->SetStringField(TEXT("blendMode"), BlendModeToString(MaterialObj->BlendMode));
Result->SetStringField(TEXT("domain"), MCPUtils::EnumToString(MaterialObj->MaterialDomain, TEXT("MD_")));
Result->SetStringField(TEXT("blendMode"), MCPUtils::EnumToString(MaterialObj->BlendMode, TEXT("BLEND_")));
Result->SetBoolField(TEXT("twoSided"), MaterialObj->TwoSided != 0);
Result->SetBoolField(TEXT("saved"), bSaved);
}
@@ -240,72 +213,14 @@ public:
FString OldValue;
FString NewValue;
// Helper lambdas for converting enum values to strings
auto DomainToString = [](EMaterialDomain Domain) -> FString
{
switch (Domain)
{
case MD_Surface: return TEXT("Surface");
case MD_DeferredDecal: return TEXT("DeferredDecal");
case MD_LightFunction: return TEXT("LightFunction");
case MD_Volume: return TEXT("Volume");
case MD_PostProcess: return TEXT("PostProcess");
case MD_UI: return TEXT("UI");
default: return TEXT("Unknown");
}
};
auto BlendModeToString = [](EBlendMode Mode) -> FString
{
switch (Mode)
{
case BLEND_Opaque: return TEXT("Opaque");
case BLEND_Masked: return TEXT("Masked");
case BLEND_Translucent: return TEXT("Translucent");
case BLEND_Additive: return TEXT("Additive");
case BLEND_Modulate: return TEXT("Modulate");
default: return TEXT("Unknown");
}
};
auto ShadingModelToString = [](EMaterialShadingModel Model) -> FString
{
switch (Model)
{
case MSM_Unlit: return TEXT("Unlit");
case MSM_DefaultLit: return TEXT("DefaultLit");
case MSM_Subsurface: return TEXT("Subsurface");
case MSM_PreintegratedSkin: return TEXT("PreintegratedSkin");
case MSM_ClearCoat: return TEXT("ClearCoat");
case MSM_SubsurfaceProfile: return TEXT("SubsurfaceProfile");
case MSM_TwoSidedFoliage: return TEXT("TwoSidedFoliage");
case MSM_Hair: return TEXT("Hair");
case MSM_Cloth: return TEXT("Cloth");
case MSM_Eye: return TEXT("Eye");
default: return TEXT("DefaultLit");
}
};
if (Property == TEXT("domain"))
{
FString ValueStr = Json->GetStringField(TEXT("value"));
OldValue = DomainToString(MaterialObj->MaterialDomain);
OldValue = MCPUtils::EnumToString(MaterialObj->MaterialDomain, TEXT("MD_"));
EMaterialDomain NewDomain = MaterialObj->MaterialDomain;
if (ValueStr == TEXT("Surface")) NewDomain = MD_Surface;
else if (ValueStr == TEXT("DeferredDecal")) NewDomain = MD_DeferredDecal;
else if (ValueStr == TEXT("LightFunction")) NewDomain = MD_LightFunction;
else if (ValueStr == TEXT("Volume")) NewDomain = MD_Volume;
else if (ValueStr == TEXT("PostProcess")) NewDomain = MD_PostProcess;
else if (ValueStr == TEXT("UI")) NewDomain = MD_UI;
else
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Invalid domain '%s'. Valid values: Surface, DeferredDecal, LightFunction, Volume, PostProcess, UI"),
*ValueStr));
}
NewValue = ValueStr;
EMaterialDomain NewDomain;
if (!MCPUtils::StringToEnum(ValueStr, NewDomain, Result, TEXT("MD_"))) return;
NewValue = MCPUtils::EnumToString(NewDomain, TEXT("MD_"));
if (!DryRun)
{
@@ -317,22 +232,11 @@ public:
else if (Property == TEXT("blendMode"))
{
FString ValueStr = Json->GetStringField(TEXT("value"));
OldValue = BlendModeToString(MaterialObj->BlendMode);
OldValue = MCPUtils::EnumToString(MaterialObj->BlendMode, TEXT("BLEND_"));
EBlendMode NewBlend = MaterialObj->BlendMode;
if (ValueStr == TEXT("Opaque")) NewBlend = BLEND_Opaque;
else if (ValueStr == TEXT("Masked")) NewBlend = BLEND_Masked;
else if (ValueStr == TEXT("Translucent")) NewBlend = BLEND_Translucent;
else if (ValueStr == TEXT("Additive")) NewBlend = BLEND_Additive;
else if (ValueStr == TEXT("Modulate")) NewBlend = BLEND_Modulate;
else
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Invalid blendMode '%s'. Valid values: Opaque, Masked, Translucent, Additive, Modulate"),
*ValueStr));
}
NewValue = ValueStr;
EBlendMode NewBlend;
if (!MCPUtils::StringToEnum(ValueStr, NewBlend, Result, TEXT("BLEND_"))) return;
NewValue = MCPUtils::EnumToString(NewBlend, TEXT("BLEND_"));
if (!DryRun)
{
@@ -357,27 +261,11 @@ public:
else if (Property == TEXT("shadingModel"))
{
FString ValueStr = Json->GetStringField(TEXT("value"));
OldValue = ShadingModelToString(MaterialObj->GetShadingModels().GetFirstShadingModel());
OldValue = MCPUtils::EnumToString(MaterialObj->GetShadingModels().GetFirstShadingModel(), TEXT("MSM_"));
EMaterialShadingModel NewModel = MSM_DefaultLit;
if (ValueStr == TEXT("Unlit")) NewModel = MSM_Unlit;
else if (ValueStr == TEXT("DefaultLit")) NewModel = MSM_DefaultLit;
else if (ValueStr == TEXT("Subsurface")) NewModel = MSM_Subsurface;
else if (ValueStr == TEXT("PreintegratedSkin")) NewModel = MSM_PreintegratedSkin;
else if (ValueStr == TEXT("ClearCoat")) NewModel = MSM_ClearCoat;
else if (ValueStr == TEXT("SubsurfaceProfile")) NewModel = MSM_SubsurfaceProfile;
else if (ValueStr == TEXT("TwoSidedFoliage")) NewModel = MSM_TwoSidedFoliage;
else if (ValueStr == TEXT("Hair")) NewModel = MSM_Hair;
else if (ValueStr == TEXT("Cloth")) NewModel = MSM_Cloth;
else if (ValueStr == TEXT("Eye")) NewModel = MSM_Eye;
else
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(
TEXT("Invalid shadingModel '%s'. Valid values: Unlit, DefaultLit, Subsurface, PreintegratedSkin, ClearCoat, SubsurfaceProfile, TwoSidedFoliage, Hair, Cloth, Eye"),
*ValueStr));
}
NewValue = ValueStr;
EMaterialShadingModel NewModel;
if (!MCPUtils::StringToEnum(ValueStr, NewModel, Result, TEXT("MSM_"))) return;
NewValue = MCPUtils::EnumToString(NewModel, TEXT("MSM_"));
if (!DryRun)
{

View File

@@ -112,7 +112,9 @@ public:
{
if (PinInfo.IsValid() && PinInfo->PinName.ToString().Equals(ParamName, ESearchCase::IgnoreCase))
{
EntryNode->PreEditChange(nullptr);
PinInfo->PinType = NewPinType;
EntryNode->PostEditChange();
bPinFound = true;
break;
}

View File

@@ -332,6 +332,7 @@ public:
// Update properties
int32 ChangedCount = 0;
TransNode->PreEditChange(nullptr);
if (Json->HasField(TEXT("crossfadeDuration")))
{
@@ -363,6 +364,7 @@ public:
{
return MCPUtils::MakeErrorJson(Result, TEXT("No properties to update. Provide at least one of: crossfadeDuration, blendMode, priorityOrder, logicType, bBidirectional"));
}
TransNode->PostEditChange();
// Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP);

View File

@@ -103,15 +103,7 @@ public:
WarningsArr.Add(MakeShared<FJsonValueObject>(Msg));
}
FString StatusStr;
switch (BP->Status)
{
case BS_UpToDate: StatusStr = TEXT("UpToDate"); break;
case BS_Dirty: StatusStr = TEXT("Dirty"); break;
case BS_Error: StatusStr = TEXT("Error"); break;
case BS_Unknown: StatusStr = TEXT("Unknown"); break;
default: StatusStr = FString::Printf(TEXT("Status_%d"), (int32)BP->Status); break;
}
FString StatusStr = MCPUtils::EnumToString((EBlueprintStatus)BP->Status, TEXT("BS_"));
bool bIsValid = (BP->Status == BS_UpToDate) && (ErrorsArr.Num() == 0);
@@ -129,17 +121,14 @@ public:
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
MCPAssets<UBlueprint> Finder;
Finder.Scan<UBlueprint>().Scan<UWorld>();
Finder.Scan<UBlueprint>().Scan<UWorld>().Errors(Result);
if (!Blueprint.IsEmpty())
{
if (!Finder.Exact(Blueprint).ETwo().Info() || Finder.AllData().IsEmpty())
{
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Blueprint '%s' not found."), *Blueprint));
}
if (!Finder.Exact(Blueprint).ENone().ETwo().Info()) return;
}
else
{
Finder.Substring(Query).Info();
if (!Finder.Substring(Query).Info()) return;
}
const TArray<FAssetData>& MatchingAssets = Finder.AllData();

View File

@@ -158,6 +158,7 @@ public:
}
// Directly modify the variable type in the description array.
BP->PreEditChange(nullptr);
for (FBPVariableDescription& Var : BP->NewVariables)
{
if (Var.VarName == FName(*Variable))
@@ -166,6 +167,7 @@ public:
break;
}
}
BP->PostEditChange();
// Save
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
@@ -563,6 +565,8 @@ public:
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: SetVariableMetadata on '%s.%s' — %d field(s) changed"),
*Blueprint, *Variable, Changes.Num());
BP->PreEditChange(nullptr);
BP->PostEditChange();
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);

View File

@@ -67,7 +67,19 @@ public:
}
};
struct MCPErrorCallback;
// ----- Error callback -----
struct MCPErrorCallback
{
TFunction<void(const FString&)> Func;
MCPErrorCallback(std::nullptr_t);
MCPErrorCallback(FString& OutError);
MCPErrorCallback(FJsonObject* Result);
void SetError(const FString& Msg) const { Func(Msg); }
};
// Stateless utility functions used by MCP handlers and the MCP server.
// This is effectively a namespace — all methods are static.
@@ -97,6 +109,34 @@ public:
static void CopyJsonFields(const FJsonObject* Source, FJsonObject* Dest);
static FString UrlDecode(const FString& EncodedString);
// ----- Enum helpers -----
// Convert enum value to string. If Prefix is specified, strip "Prefix_" from the front.
template<typename T>
static FString EnumToString(T Value, const FString& Prefix = FString())
{
UEnum* Enum = StaticEnum<T>();
FString Full = Enum->GetNameStringByValue((int64)Value);
if (!Prefix.IsEmpty() && Full.StartsWith(Prefix))
return Full.Mid(Prefix.Len());
return Full;
}
// Convert string to enum value. If Prefix is specified, prepend it before lookup.
// Returns false and sets error if the string doesn't match any value.
template<typename T>
static bool StringToEnum(const FString& Str, T& OutValue, MCPErrorCallback Error, const FString& Prefix = FString())
{
UEnum* Enum = StaticEnum<T>();
int64 Value = Enum->GetValueByNameString(Prefix + Str);
if (Value == INDEX_NONE)
{
Error.SetError(FString::Printf(TEXT("Invalid value '%s' for %s"), *Str, *Enum->GetName()));
return false;
}
OutValue = (T)Value;
return true;
}
// ----- Blueprint helpers -----
static TArray<UEdGraph*> AllGraphs(UBlueprint* BP);
static TArray<UEdGraph*> AllGraphsNamed(UBlueprint* BP, const FString& Name);
@@ -155,17 +195,3 @@ public:
private:
static FString SetPropertyFromJson(void* Container, FProperty* Prop, const FString& FieldName, const FJsonObject* Json);
};
// ----- Error callback -----
struct MCPErrorCallback
{
TFunction<void(const FString&)> Func;
MCPErrorCallback(std::nullptr_t);
MCPErrorCallback(FString& OutError);
MCPErrorCallback(FJsonObject* Result);
void SetError(const FString& Msg) const { Func(Msg); }
};