Lots of useful work

This commit is contained in:
2026-03-17 17:24:35 -04:00
parent a1131130af
commit cad9947670
27 changed files with 208 additions and 277 deletions

Binary file not shown.

View File

@@ -5,6 +5,7 @@
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPAssets.h" #include "MCPAssets.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "MCPTypes.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Engine/World.h" #include "Engine/World.h"
#include "Blueprint_Search.generated.h" #include "Blueprint_Search.generated.h"
@@ -26,12 +27,6 @@ public:
UPROPERTY(meta=(Optional, Description="Filter by parent class name (exact match, case-insensitive)")) UPROPERTY(meta=(Optional, Description="Filter by parent class name (exact match, case-insensitive)"))
FString ParentClass; FString ParentClass;
UPROPERTY(meta=(Optional, Description="Include regular blueprints (default true)"))
bool IncludeRegular = true;
UPROPERTY(meta=(Optional, Description="Include level blueprints (default true)"))
bool IncludeLevel = true;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("List all Blueprint assets in the project, with optional filtering by name, parent class, or type."); return TEXT("List all Blueprint assets in the project, with optional filtering by name, parent class, or type.");
@@ -40,22 +35,21 @@ public:
virtual void Handle() override virtual void Handle() override
{ {
MCPAssets<UObject> Assets; MCPAssets<UObject> Assets;
Assets.NoScans().Substring(Query).Limit(500); Assets.Scan<UBlueprint>().Substring(Query).Limit(500);
if (IncludeRegular) Assets.Scan<UBlueprint>(); if (!Assets.Info()) return;
if (IncludeLevel) Assets.Scan<UWorld>();
Assets.Info(); UClass *Parent = nullptr;
if (!ParentClass.IsEmpty())
{
Parent = UMCPTypes::TextToOneObjectType(ParentClass);
if (!Parent) return;
}
int32 Count = 0; int32 Count = 0;
for (const FAssetData& Asset : Assets.AllData()) for (const FAssetData& Asset : Assets.AllData())
{ {
// Extract parent class name from asset tags // Extract parent class name from asset tags
FString ParentClassName; FString ParentClassName;
if (Asset.AssetClassPath == UWorld::StaticClass()->GetClassPathName())
{
ParentClassName = TEXT("LevelScriptActor");
}
else
{
Asset.GetTagValue(FName(TEXT("ParentClass")), ParentClassName); Asset.GetTagValue(FName(TEXT("ParentClass")), ParentClassName);
int32 DotIndex; int32 DotIndex;
if (ParentClassName.FindLastChar('.', DotIndex)) if (ParentClassName.FindLastChar('.', DotIndex))
@@ -63,7 +57,6 @@ public:
ParentClassName = ParentClassName.Mid(DotIndex + 1); ParentClassName = ParentClassName.Mid(DotIndex + 1);
} }
ParentClassName.RemoveFromEnd(TEXT("'")); ParentClassName.RemoveFromEnd(TEXT("'"));
}
// Apply parent class filter // Apply parent class filter
if (!ParentClass.IsEmpty()) if (!ParentClass.IsEmpty())

View File

@@ -1,83 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "MCPServer.h"
#include "MCPHandler.h"
#include "MCPUtils.h"
#include "Materials/Material.h"
#include "MaterialDomain.h"
#include "Factories/MaterialFactoryNew.h"
#include "MCPPackageMaker.h"
#include "Material_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCP_Material_Create : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
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;
UPROPERTY(meta=(Optional, Description="Blend mode: Opaque, Masked, Translucent, Additive, Modulate"))
FString BlendMode;
UPROPERTY(meta=(Optional, Description="Whether the material is two-sided"))
bool TwoSided = false;
virtual FString GetDescription() const override
{
return TEXT("Create a new UMaterial asset with optional domain, blend mode, and two-sided settings.");
}
virtual void Handle() override
{
MCPPackageMaker Maker(AssetPath);
if (!Maker.Ok()) return;
// Parse optional enum properties before creating the asset.
EMaterialDomain ParsedDomain = MD_Surface;
if (!Domain.IsEmpty())
{
if (!MCPUtils::StringToEnum(Domain, ParsedDomain, TEXT("MD_")))
return;
}
EBlendMode ParsedBlendMode = BLEND_Opaque;
if (!BlendMode.IsEmpty())
{
if (!MCPUtils::StringToEnum(BlendMode, ParsedBlendMode, TEXT("BLEND_")))
return;
}
// Create via IAssetTools + factory.
UMaterial* MaterialObj = Maker.CreateAsset<UMaterial, UMaterialFactoryNew>();
if (!MaterialObj) return;
// Apply optional properties.
if (!Domain.IsEmpty())
MaterialObj->MaterialDomain = ParsedDomain;
if (!BlendMode.IsEmpty())
MaterialObj->BlendMode = ParsedBlendMode;
MaterialObj->TwoSided = TwoSided ? 1 : 0;
bool bSaved = MCPUtils::SaveGenericPackage(MaterialObj);
UMCPServer::Printf(TEXT("Created %s\n"), *MaterialObj->GetPathName());
UMCPServer::Printf(TEXT("Domain: %s\n"), *MCPUtils::EnumToString(MaterialObj->MaterialDomain, TEXT("MD_")));
UMCPServer::Printf(TEXT("BlendMode: %s\n"), *MCPUtils::EnumToString(MaterialObj->BlendMode, TEXT("BLEND_")));
UMCPServer::Printf(TEXT("TwoSided: %s\n"), MaterialObj->TwoSided ? TEXT("true") : TEXT("false"));
if (!bSaved)
UMCPServer::Print(TEXT("WARNING: Package save failed\n"));
}
};

View File

@@ -42,7 +42,7 @@ public:
virtual void Handle() override virtual void Handle() override
{ {
MCPFetcher F; MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
// Check for duplicate variable name // Check for duplicate variable name

View File

@@ -35,7 +35,7 @@ public:
virtual void Handle() override virtual void Handle() override
{ {
MCPFetcher F; MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
FBPVarEditor Editor(BP, Variable); FBPVarEditor Editor(BP, Variable);

View File

@@ -35,7 +35,7 @@ public:
virtual void Handle() override virtual void Handle() override
{ {
MCPFetcher F; MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
FBPVarEditor Editor(BP, Variable); FBPVarEditor Editor(BP, Variable);

View File

@@ -41,7 +41,7 @@ public:
virtual void Handle() override virtual void Handle() override
{ {
MCPFetcher F; MCPFetcher F;
UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast<UBlueprint>(); UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return; if (!BP) return;
FBPVarEditor Editor(BP, Variable); FBPVarEditor Editor(BP, Variable);

View File

@@ -6,6 +6,7 @@
#include "MCPToolMenu.h" #include "MCPToolMenu.h"
#include "MCPServer.h" #include "MCPServer.h"
#include "ToolMenus.h" #include "ToolMenus.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "GraphNode_ShowMenu.generated.h" #include "GraphNode_ShowMenu.generated.h"
@@ -34,6 +35,11 @@ private:
UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>(); UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>();
if (!NodeObj) return; if (!NodeObj) return;
if (Cast<UMaterialGraphNode>(NodeObj))
{
UMCPServer::Printf(TEXT("Material graph nodes do not have usable context menus."));
return;
}
FToolMenuContext Context; FToolMenuContext Context;
TArray<FToolMenuEntry> Entries = MCPToolMenu::GetMenuItems(NodeObj, Context); TArray<FToolMenuEntry> Entries = MCPToolMenu::GetMenuItems(NodeObj, Context);
for (FToolMenuEntry &Entry : Entries) for (FToolMenuEntry &Entry : Entries)
@@ -41,5 +47,6 @@ private:
FString LabelText = Entry.Label.Get().ToString(); FString LabelText = Entry.Label.Get().ToString();
UMCPServer::Printf(TEXT("%s\n"), *LabelText); UMCPServer::Printf(TEXT("%s\n"), *LabelText);
} }
if (Entries.IsEmpty()) UMCPServer::Printf(TEXT("No selectable menu entries right now.\n"));
} }
}; };

View File

@@ -50,7 +50,7 @@ public:
virtual void Handle() override virtual void Handle() override
{ {
MCPFetcher F; MCPFetcher F;
UEdGraph* G = F.Walk(Graph).ToGraph().Cast<UEdGraph>(); UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>();
if (!G) return; if (!G) return;
int32 SuccessCount = 0; int32 SuccessCount = 0;

View File

@@ -50,7 +50,7 @@ public:
virtual void Handle() override virtual void Handle() override
{ {
MCPFetcher F; MCPFetcher F;
UEdGraph* G = F.Walk(Graph).ToGraph().Cast<UEdGraph>(); UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>();
if (!G) return; if (!G) return;
int32 SuccessCount = 0; int32 SuccessCount = 0;

View File

@@ -37,7 +37,7 @@ public:
virtual void Handle() override virtual void Handle() override
{ {
MCPFetcher F; MCPFetcher F;
UEdGraph *G = F.Walk(Graph).ToGraph().Cast<UEdGraph>(); UEdGraph *G = F.Walk(Graph).Cast<UEdGraph>();
if (!G) return; if (!G) return;
MCPGraphExport Exporter(G); MCPGraphExport Exporter(G);

View File

@@ -5,6 +5,7 @@
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "MaterialParameter.h"
#include "Materials/MaterialInstanceConstant.h" #include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h" #include "MaterialTypes.h"
#include "MaterialInstance_ClearParameter.generated.h" #include "MaterialInstance_ClearParameter.generated.h"
@@ -45,7 +46,7 @@ public:
// Parse the association string. // Parse the association string.
EMaterialParameterAssociation Association; EMaterialParameterAssociation Association;
if (!MCPUtils::ParseMaterialParameterAssociation(ParameterAssociation, Association)) if (!MCPMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association))
return; return;
FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer); FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer);

View File

@@ -5,6 +5,7 @@
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "MaterialParameter.h"
#include "Materials/MaterialInstanceConstant.h" #include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h" #include "MaterialTypes.h"
#include "MaterialInstance_DumpParameters.generated.h" #include "MaterialInstance_DumpParameters.generated.h"
@@ -34,7 +35,7 @@ public:
UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>(); UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>();
if (!MI) return; if (!MI) return;
auto AllParams = MCPUtils::GetMaterialParameters(MI); auto AllParams = MCPMaterialParameter::GetMaterialParameters(MI);
// Overridden parameters first. // Overridden parameters first.
bool bHasOverrides = false; bool bHasOverrides = false;
@@ -42,7 +43,7 @@ public:
{ {
if (!Meta.bOverride) continue; if (!Meta.bOverride) continue;
if (!bHasOverrides) { UMCPServer::Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; } if (!bHasOverrides) { UMCPServer::Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; }
MCPUtils::FormatMaterialParameter(Info, Meta); MCPMaterialParameter::FormatMaterialParameter(Info, Meta);
} }
// Inherited (non-overridden) parameters. // Inherited (non-overridden) parameters.
@@ -51,7 +52,7 @@ public:
{ {
if (Meta.bOverride) continue; if (Meta.bOverride) continue;
if (!bHasInherited) { UMCPServer::Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; } if (!bHasInherited) { UMCPServer::Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; }
MCPUtils::FormatMaterialParameter(Info, Meta); MCPMaterialParameter::FormatMaterialParameter(Info, Meta);
} }
} }
}; };

View File

@@ -5,6 +5,7 @@
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "MaterialParameter.h"
#include "Materials/MaterialInstanceConstant.h" #include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h" #include "MaterialTypes.h"
#include "Misc/DefaultValueHelper.h" #include "Misc/DefaultValueHelper.h"
@@ -49,14 +50,14 @@ public:
// Parse the association string. // Parse the association string.
EMaterialParameterAssociation Association; EMaterialParameterAssociation Association;
if (!MCPUtils::ParseMaterialParameterAssociation(ParameterAssociation, Association)) if (!MCPMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association))
return; return;
// Build the parameter ID to look up. // Build the parameter ID to look up.
FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer); FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer);
// Find it in the material's parameter map. // Find it in the material's parameter map.
auto AllParams = MCPUtils::GetMaterialParameters(MI); auto AllParams = MCPMaterialParameter::GetMaterialParameters(MI);
FMaterialParameterMetadata* Found = AllParams.Find(ParamID); FMaterialParameterMetadata* Found = AllParams.Find(ParamID);
if (!Found) if (!Found)
{ {

View File

@@ -0,0 +1,45 @@
#pragma once
#include "CoreMinimal.h"
#include "MCPServer.h"
#include "MCPHandler.h"
#include "MCPUtils.h"
#include "Materials/Material.h"
#include "MaterialDomain.h"
#include "Factories/MaterialFactoryNew.h"
#include "MCPPackageMaker.h"
#include "Material_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCP_Material_Create : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new material"))
FString Material;
virtual FString GetDescription() const override
{
return TEXT("Create a new UMaterial asset");
}
virtual void Handle() override
{
MCPPackageMaker Maker(Material);
if (!Maker.Ok()) return;
// Create via IAssetTools + factory.
UMaterial* MaterialObj = Maker.CreateAsset<UMaterial, UMaterialFactoryNew>();
if (!MaterialObj) return;
bool bSaved = MCPUtils::SaveGenericPackage(MaterialObj);
UMCPServer::Printf(TEXT("Created %s\n"), *MaterialObj->GetPathName());
if (!bSaved) UMCPServer::Print(TEXT("WARNING: Package save failed\n"));
}
};

View File

@@ -3,8 +3,9 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPServer.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Materials/Material.h" #include "MaterialParameter.h"
#include "MaterialTypes.h" #include "MaterialTypes.h"
#include "Material_DumpParameters.generated.h" #include "Material_DumpParameters.generated.h"
@@ -33,11 +34,12 @@ public:
UMaterial* Mat = F.Asset(Material).Cast<UMaterial>(); UMaterial* Mat = F.Asset(Material).Cast<UMaterial>();
if (!Mat) return; if (!Mat) return;
auto AllParams = MCPUtils::GetMaterialParameters(Mat); auto AllParams = MCPMaterialParameter::GetMaterialParameters(Mat);
for (auto& [Info, Meta] : AllParams) for (auto& [Info, Meta] : AllParams)
{ {
MCPUtils::FormatMaterialParameter(Info, Meta); MCPMaterialParameter::FormatMaterialParameter(Info, Meta);
} }
if (AllParams.IsEmpty()) UMCPServer::Printf(TEXT("No material parameters.\n"));
} }
}; };

View File

@@ -78,8 +78,8 @@ public:
{ {
UMCPServer::Printf(TEXT("\n")); UMCPServer::Printf(TEXT("\n"));
EmitCommandList(false); EmitCommandList(false);
UMCPServer::Print(TEXT("\n--- Half-Baked (may have issues) ---\n\n")); // UMCPServer::Print(TEXT("\n--- Half-Baked (may have issues) ---\n\n"));
EmitCommandList(true); // EmitCommandList(true);
UMCPServer::Printf(TEXT("\n")); UMCPServer::Printf(TEXT("\n"));
} }
}; };

View File

@@ -35,12 +35,10 @@ public:
"\n component — move from a blueprint to a component" "\n component — move from a blueprint to a component"
"\n levelblueprint — move from a world to a blueprint" "\n levelblueprint — move from a world to a blueprint"
"\n" "\n"
"\n DUMP COMMANDS:" "\n Steps do not always require a parameter. For example, materials"
"\n only have one graph, so you can just say:"
"\n" "\n"
"\n There are several commands whose names end in 'Dump'. These" "\n /Game/Materials/MyMaterial,graph"
"\n are essential tools for viewing the state of the world."
"\n They are particularly important because they show you what"
"\n unique IDs exist that you can refer to."
"\n" "\n"
"\n TYPES:" "\n TYPES:"
"\n" "\n"
@@ -80,6 +78,12 @@ public:
"\n properties, which actually come from the material expressions." "\n properties, which actually come from the material expressions."
"\n You can edit these using Property_Set on the node." "\n You can edit these using Property_Set on the node."
"\n" "\n"
"\n COMMANDS YOU SHOULD KNOW ABOUT AND REMEMBER:"
"\n"
"\n UserManual: this explanation"
"\n ShowCommands: a full list of all the commands"
"\n Graph_Dump: a detailed listing of any UEdGraph"
"\n Property_Dump: show information on many objects"
"\n" "\n"
)); ));
} }

View File

@@ -25,7 +25,6 @@ MCPFetcher::WalkFunc MCPFetcher::GetWalker(const FString& Step)
return nullptr; return nullptr;
} }
void MCPFetcher::SetObj(UObject* InObj) void MCPFetcher::SetObj(UObject* InObj)
{ {
UMCPServer::AddTouchedObject(InObj); UMCPServer::AddTouchedObject(InObj);
@@ -49,15 +48,26 @@ MCPFetcher& MCPFetcher::SetError()
return *this; return *this;
} }
void MCPFetcher::PathFailed(const TCHAR* Expected)
{
SetError();
if (ResultPin)
UMCPServer::Printf(TEXT("ERROR: Path specifies a pin, but expected %s\n"), Expected);
else if (Obj)
UMCPServer::Printf(TEXT("ERROR: Path specifies a %s, but expected %s\n"), *Obj->GetClass()->GetName(), Expected);
else
UMCPServer::Printf(TEXT("ERROR: Path led to a null pointer\n"));
}
MCPFetcher& MCPFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expected) MCPFetcher& MCPFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expected)
{ {
bError = true; SetError();
if (ResultPin) if (ResultPin)
UMCPServer::Printf(TEXT("ERROR: Input to '%s' is a pin, expected %s\n"), Walker, Expected); UMCPServer::Printf(TEXT("ERROR: Input to '%s' is a pin, but expected %s\n"), Walker, Expected);
else if (Obj) else if (Obj)
UMCPServer::Printf(TEXT("ERROR: Input to '%s' is %s, expected %s\n"), Walker, *Obj->GetClass()->GetName(), Expected); UMCPServer::Printf(TEXT("ERROR: Input to '%s' is %s, but expected %s\n"), Walker, *Obj->GetClass()->GetName(), Expected);
else else
UMCPServer::Printf(TEXT("ERROR: Input to '%s' is null, expected %s\n"), Walker, Expected); UMCPServer::Printf(TEXT("ERROR: Path led to a null pointer\n"));
return *this; return *this;
} }
@@ -334,47 +344,3 @@ MCPFetcher& MCPFetcher::LevelBlueprint(const FString& Value)
return *this; return *this;
} }
MCPFetcher& MCPFetcher::ToBlueprint()
{
if (bError) return *this;
if (::Cast<UBlueprint>(Obj)) return *this;
if (UWorld* World = ::Cast<UWorld>(Obj))
{
if (!World->PersistentLevel)
{
UMCPServer::Print(TEXT("ERROR: ToBlueprint: World has no PersistentLevel\n"));
return SetError();
}
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
if (!LevelBP)
{
UMCPServer::Print(TEXT("ERROR: ToBlueprint: World has no level blueprint\n"));
return SetError();
}
SetObj(LevelBP);
return *this;
}
return TypeMismatch(TEXT("ToBlueprint"), TEXT("Blueprint or World"));
}
MCPFetcher& MCPFetcher::ToGraph()
{
if (bError) return *this;
if (::Cast<UEdGraph>(Obj)) return *this;
if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
{
MCPUtils::EnsureMaterialGraph(Mat);
if (!Mat->MaterialGraph)
{
UMCPServer::Printf(TEXT("ERROR: ToGraph: Material '%s' has no material graph\n"), *Mat->GetName());
return SetError();
}
SetObj(Mat->MaterialGraph);
return *this;
}
return TypeMismatch(TEXT("ToGraph"), TEXT("Graph or Material"));
}

View File

@@ -423,9 +423,6 @@ void UMCPServer::ClientThreadFunc(UMCPServer* Server, TSharedPtr<FClientConnecti
// Block until the game thread processes this message // Block until the game thread processes this message
FString Response = Future.Get(); FString Response = Future.Get();
// Empty response means either notification or shutdown
if (Response.IsEmpty()) continue;
// Write the response back, null-terminated (blocking) // Write the response back, null-terminated (blocking)
FTCHARToUTF8 Utf8(*Response); FTCHARToUTF8 Utf8(*Response);
int32 BytesSent = 0; int32 BytesSent = 0;

View File

@@ -514,81 +514,6 @@ UMaterial* MCPUtils::ReplaceMaterialWithTransientCopy(UMaterial* Material)
return Material; return Material;
} }
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> MCPUtils::GetMaterialParameters(UMaterialInterface* Material)
{
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Result;
if (!Material) return Result;
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Temp;
for (int32 i = 0; i < (int32)EMaterialParameterType::NumRuntime; i++)
{
Material->GetAllParametersOfType((EMaterialParameterType)i, Temp);
Result.Append(Temp);
}
return Result;
}
bool MCPUtils::ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation)
{
if (Str.Equals(TEXT("Global"), ESearchCase::IgnoreCase))
OutAssociation = GlobalParameter;
else if (Str.Equals(TEXT("Layer"), ESearchCase::IgnoreCase))
OutAssociation = LayerParameter;
else if (Str.Equals(TEXT("Blend"), ESearchCase::IgnoreCase))
OutAssociation = BlendParameter;
else
{
UMCPServer::Printf(TEXT("ERROR: Invalid ParameterAssociation '%s' (expected 'Global', 'Layer', or 'Blend')\n"), *Str);
return false;
}
return true;
}
void MCPUtils::FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta)
{
// Association prefix for layer/blend parameters.
FString Prefix;
if (Info.Association == LayerParameter)
Prefix = FString::Printf(TEXT("[Layer %d] "), Info.Index);
else if (Info.Association == BlendParameter)
Prefix = FString::Printf(TEXT("[Blend %d] "), Info.Index);
switch (Meta.Value.Type)
{
case EMaterialParameterType::Scalar:
UMCPServer::Printf(TEXT(" %sScalar \"%s\" = %g\n"), *Prefix, *Info.Name.ToString(), Meta.Value.AsScalar());
break;
case EMaterialParameterType::Vector:
{
FLinearColor C = Meta.Value.AsLinearColor();
UMCPServer::Printf(TEXT(" %sVector \"%s\" = (R=%.3f, G=%.3f, B=%.3f, A=%.3f)\n"),
*Prefix, *Info.Name.ToString(), C.R, C.G, C.B, C.A);
break;
}
case EMaterialParameterType::DoubleVector:
{
FVector4d V = Meta.Value.AsVector4d();
UMCPServer::Printf(TEXT(" %sDoubleVector \"%s\" = (%.3f, %.3f, %.3f, %.3f)\n"),
*Prefix, *Info.Name.ToString(), V.X, V.Y, V.Z, V.W);
break;
}
case EMaterialParameterType::Texture:
{
UTexture* Tex = Cast<UTexture>(Meta.Value.AsTextureObject());
UMCPServer::Printf(TEXT(" %sTexture \"%s\" = %s\n"),
*Prefix, *Info.Name.ToString(), Tex ? *MCPUtils::FormatName(Tex) : TEXT("None"));
break;
}
case EMaterialParameterType::StaticSwitch:
UMCPServer::Printf(TEXT(" %sStaticSwitch \"%s\" = %s\n"),
*Prefix, *Info.Name.ToString(), Meta.Value.AsStaticSwitch() ? TEXT("true") : TEXT("false"));
break;
default:
UMCPServer::Printf(TEXT(" %sType%d \"%s\"\n"), *Prefix, (int)Meta.Value.Type, *Info.Name.ToString());
break;
}
}
bool MCPUtils::SaveGenericPackage(UObject* Asset) bool MCPUtils::SaveGenericPackage(UObject* Asset)
{ {
if (!Asset) return false; if (!Asset) return false;

View File

@@ -0,0 +1,78 @@
#include "MaterialParameter.h"
#include "MCPUtils.h"
#include "MCPServer.h"
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> MCPMaterialParameter::GetMaterialParameters(UMaterialInterface* Material)
{
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Result;
if (!Material) return Result;
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Temp;
for (int32 i = 0; i < (int32)EMaterialParameterType::NumRuntime; i++)
{
Material->GetAllParametersOfType((EMaterialParameterType)i, Temp);
Result.Append(Temp);
}
return Result;
}
bool MCPMaterialParameter::ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation)
{
if (Str.Equals(TEXT("Global"), ESearchCase::IgnoreCase))
OutAssociation = GlobalParameter;
else if (Str.Equals(TEXT("Layer"), ESearchCase::IgnoreCase))
OutAssociation = LayerParameter;
else if (Str.Equals(TEXT("Blend"), ESearchCase::IgnoreCase))
OutAssociation = BlendParameter;
else
{
UMCPServer::Printf(TEXT("ERROR: Invalid ParameterAssociation '%s' (expected 'Global', 'Layer', or 'Blend')\n"), *Str);
return false;
}
return true;
}
void MCPMaterialParameter::FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta)
{
// Association prefix for layer/blend parameters.
FString Prefix;
if (Info.Association == LayerParameter)
Prefix = FString::Printf(TEXT("[Layer %d] "), Info.Index);
else if (Info.Association == BlendParameter)
Prefix = FString::Printf(TEXT("[Blend %d] "), Info.Index);
switch (Meta.Value.Type)
{
case EMaterialParameterType::Scalar:
UMCPServer::Printf(TEXT(" %sScalar \"%s\" = %g\n"), *Prefix, *Info.Name.ToString(), Meta.Value.AsScalar());
break;
case EMaterialParameterType::Vector:
{
FLinearColor C = Meta.Value.AsLinearColor();
UMCPServer::Printf(TEXT(" %sVector \"%s\" = (R=%.3f, G=%.3f, B=%.3f, A=%.3f)\n"),
*Prefix, *Info.Name.ToString(), C.R, C.G, C.B, C.A);
break;
}
case EMaterialParameterType::DoubleVector:
{
FVector4d V = Meta.Value.AsVector4d();
UMCPServer::Printf(TEXT(" %sDoubleVector \"%s\" = (%.3f, %.3f, %.3f, %.3f)\n"),
*Prefix, *Info.Name.ToString(), V.X, V.Y, V.Z, V.W);
break;
}
case EMaterialParameterType::Texture:
{
UTexture* Tex = Cast<UTexture>(Meta.Value.AsTextureObject());
UMCPServer::Printf(TEXT(" %sTexture \"%s\" = %s\n"),
*Prefix, *Info.Name.ToString(), Tex ? *MCPUtils::FormatName(Tex) : TEXT("None"));
break;
}
case EMaterialParameterType::StaticSwitch:
UMCPServer::Printf(TEXT(" %sStaticSwitch \"%s\" = %s\n"),
*Prefix, *Info.Name.ToString(), Meta.Value.AsStaticSwitch() ? TEXT("true") : TEXT("false"));
break;
default:
UMCPServer::Printf(TEXT(" %sType%d \"%s\"\n"), *Prefix, (int)Meta.Value.Type, *Info.Name.ToString());
break;
}
}

View File

@@ -54,15 +54,6 @@ public:
MCPFetcher& Component(const FString& Value); MCPFetcher& Component(const FString& Value);
MCPFetcher& LevelBlueprint(const FString& Value); MCPFetcher& LevelBlueprint(const FString& Value);
// The following walkers cannot be invoked from
// paths, only procedurally. If the current object
// is already the target type, they do nothing.
// Otherwise, they attempt to convert (e.g. World
// to its level blueprint, Material to its graph).
//
MCPFetcher& ToBlueprint();
MCPFetcher& ToGraph();
// Return true if there haven't been any errors. // Return true if there haven't been any errors.
// Note that errors always automatically generate // Note that errors always automatically generate
// output to MCPServer::Printf. // output to MCPServer::Printf.
@@ -77,8 +68,7 @@ public:
{ {
if (bError) return nullptr; if (bError) return nullptr;
T* Result = ::Cast<T>(Obj); T* Result = ::Cast<T>(Obj);
if (Result == nullptr) if (Result == nullptr) PathFailed(*T::StaticClass()->GetName());
TypeMismatch(TEXT("Cast"), *T::StaticClass()->GetName());
return Result; return Result;
} }
@@ -144,6 +134,7 @@ private:
void SetObj(UObject* InObj); void SetObj(UObject* InObj);
void SetPin(UEdGraphPin* InPin); void SetPin(UEdGraphPin* InPin);
MCPFetcher& SetError(); MCPFetcher& SetError();
void PathFailed(const TCHAR *Kind);
MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected); MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected);
bool CheckAssetIsA(UClass* StaticClass); bool CheckAssetIsA(UClass* StaticClass);
WalkFunc GetWalker(const FString &Step); WalkFunc GetWalker(const FString &Step);
@@ -152,7 +143,6 @@ private:
template<> inline UEdGraphPin* MCPFetcher::Cast<UEdGraphPin>() template<> inline UEdGraphPin* MCPFetcher::Cast<UEdGraphPin>()
{ {
if (bError) return nullptr; if (bError) return nullptr;
if (!ResultPin) if (!ResultPin) PathFailed(TEXT("UEdGraphPin"));
TypeMismatch(TEXT("Cast"), TEXT("pin"));
return ResultPin; return ResultPin;
} }

View File

@@ -153,11 +153,6 @@ public:
static FString ActionFullName(const TSharedPtr<FEdGraphSchemaAction>& Action); static FString ActionFullName(const TSharedPtr<FEdGraphSchemaAction>& Action);
static TArray<TSharedPtr<FEdGraphSchemaAction>> SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults = 0, bool ExactMatch = false); static TArray<TSharedPtr<FEdGraphSchemaAction>> SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults = 0, bool ExactMatch = false);
// ----- Material Parameters -----
static TMap<FMaterialParameterInfo, FMaterialParameterMetadata> GetMaterialParameters(UMaterialInterface* Material);
static bool ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation);
static void FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta);
// ----- Editable template ----- // ----- Editable template -----
static TArray<FProperty*> SearchProperties(UObject* Obj, const FString& Query, EPropertyFlags Flags, bool bLocal); static TArray<FProperty*> SearchProperties(UObject* Obj, const FString& Query, EPropertyFlags Flags, bool bLocal);
static FProperty* FindPropertyByName(UObject* Obj, const FString& Name); static FProperty* FindPropertyByName(UObject* Obj, const FString& Name);

View File

@@ -0,0 +1,13 @@
#pragma once
#include "CoreMinimal.h"
#include "Materials/Material.h"
class MCPMaterialParameter
{
public:
static TMap<FMaterialParameterInfo, FMaterialParameterMetadata> GetMaterialParameters(UMaterialInterface* Material);
static bool ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation);
static void FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta);
};

View File

@@ -20,7 +20,6 @@ TOOL_DESCRIPTION = (
"Send a command to the Unreal Editor's BlueprintMCP plugin. " "Send a command to the Unreal Editor's BlueprintMCP plugin. "
"The 'command' field specifies which operation to perform; " "The 'command' field specifies which operation to perform; "
"additional fields are command-specific parameters. " "additional fields are command-specific parameters. "
'Use {"command": "ShowCommands"} to list available commands. '
'Use {"command": "UserManual"} to get an overview. ' 'Use {"command": "UserManual"} to get an overview. '
"If the editor is not running, the call will return an error; " "If the editor is not running, the call will return an error; "
"just ask the user to start the editor and try again." "just ask the user to start the editor and try again."