Working on MI SetParameter

This commit is contained in:
2026-03-12 08:26:18 -04:00
parent cf9ffc619c
commit 73b5d67b96
12 changed files with 212 additions and 40 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -63,10 +63,7 @@
"extensions": {
"recommendations": [
"llvm-vs-code-extensions.vscode-clangd",
"vadimcn.vscode-lldb",
"dfarley1.file-picker",
"ms-python.python",
"ms-vscode.mono-debug"
"vadimcn.vscode-lldb"
]
},
"tasks": {

View File

@@ -29,7 +29,9 @@ public class BlueprintMCP : ModuleRules
"MaterialEditor",
"AnimGraph",
"AnimGraphRuntime",
"RHI"
"RHI",
"Slate",
"SlateCore"
});
}
}

View File

@@ -12,7 +12,7 @@
#include "Materials/MaterialExpressionTextureSampleParameter2D.h"
#include "Materials/MaterialExpressionStaticSwitchParameter.h"
#include "Engine/Texture.h"
#include "Material_DumpInstanceParameters.generated.h"
#include "MaterialInstance_DumpParameters.generated.h"
// ---------------------------------------------------------------------------
@@ -20,7 +20,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UMCP_Material_DumpInstanceParameters : public UObject, public IMCPHandler
class UMCP_MaterialInstance_DumpParameters : public UObject, public IMCPHandler
{
GENERATED_BODY()

View File

@@ -0,0 +1,110 @@
#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPFetcher.h"
#include "MCPUtils.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Misc/DefaultValueHelper.h"
#include "MaterialInstance_SetParameter.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCP_MaterialInstance_SetParameter : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Material Instance path"))
FString Path;
UPROPERTY(meta=(Description="Parameter name to set"))
FString Parameter;
UPROPERTY(meta=(Description="Value to set (uses Unreal text format, e.g. '0.5' for scalar, '(R=1,G=0,B=0,A=1)' for vector)"))
FString Value;
virtual FString GetDescription() const override
{
return TEXT("Set a parameter override on a Material Instance.");
}
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
{
MCPFetcher F(Result);
UMaterialInstanceConstant* MI = F.Asset(Path).Cast<UMaterialInstanceConstant>();
if (!MI) return;
auto Parameters = MCPUtils::MaterialParameters(MI);
// Find a parameter with a matching name.
FName Name(*Parameter);
MCPMaterialParameter* Found = nullptr;
int MatchCount = 0;
for (auto& P : Parameters)
{
if (P.Name == Name)
{
MatchCount++;
Found = &P;
}
}
if (MatchCount > 1)
{
Result.Appendf(TEXT("More than one parameter named '%s'"), *Parameter);
return;
}
if (MatchCount == 0)
{
Result.Appendf(TEXT("No parameter named '%s'"), *Parameter);
return;
}
if (Found->Metadata.PrimitiveDataIndex != INDEX_NONE)
{
Result.Appendf(TEXT("Parameter '%s' uses custom primitive data and cannot be set on a material instance"), *Parameter);
return;
}
FMaterialParameterInfo Info(*Parameter);
EMaterialParameterType Type = Found->Metadata.Value.Type;
F.PreEdit();
switch (Type)
{
case EMaterialParameterType::Scalar:
{
float ScalarValue;
if (!FDefaultValueHelper::ParseFloat(Value, ScalarValue))
{
Result.Appendf(TEXT("Failed to parse '%s' as a float"), *Value);
return;
}
MI->SetScalarParameterValueEditorOnly(Info, ScalarValue);
break;
}
case EMaterialParameterType::Vector:
{
FLinearColor Color;
if (!Color.InitFromString(Value))
{
Result.Appendf(TEXT("Failed to parse '%s' as a color/vector (expected format: '(R=1,G=0,B=0,A=1)')"), *Value);
return;
}
MI->SetVectorParameterValueEditorOnly(Info, Color);
break;
}
default:
Result.Appendf(TEXT("Unsupported parameter type for '%s'"), *Parameter);
return;
}
F.PostEdit();
MCPUtils::SaveGenericPackage(MI);
Result.Appendf(TEXT("Set '%s' = %s on %s\n"),
*Parameter, *Value, *MCPUtils::FormatName(MI));
}
};

View File

@@ -48,7 +48,6 @@ MCPFetcher& MCPFetcher::Walk(const FString& Path)
{
if (bError) return *this;
// Split on commas
TArray<FString> Segments;
Path.ParseIntoArray(Segments, TEXT(","));
if (Segments.Num() == 0)
@@ -57,46 +56,33 @@ MCPFetcher& MCPFetcher::Walk(const FString& Path)
return *this;
}
// If no object yet, first segment is an asset path
int32 Start = 0;
for (int32 i = 0; i < Segments.Num(); i++)
{
if (!Obj && !ResultPin)
{
LoadUAsset(Segments[0]);
Asset(Segments[i]);
if (bError) return *this;
Start = 1;
continue;
}
const TArray<FWalker>& Walkers = GetWalkerTable();
// Walk each subsequent segment
for (int32 i = Start; i < Segments.Num(); i++)
{
FString Key, Value;
if (!Segments[i].Split(TEXT(":"), &Key, &Value))
Key = Segments[i];
bool bFound = false;
for (const FWalker& W : Walkers)
const FWalker* W = GetWalker(Key);
if (!W)
{
if (!StrEq(Key, W.Key)) continue;
(this->*W.Func)(Value);
bFound = true;
break;
}
if (!bFound)
{
SetError(FString::Printf(TEXT("Unknown walker '%s'"), *Key));
SetError(FString::Printf(TEXT("Unknown path step '%s'"), *Key));
return *this;
}
(this->*W->Func)(Value);
if (bError) return *this;
}
return *this;
}
void MCPFetcher::LoadUAsset(const FString& PackagePath)
MCPFetcher& MCPFetcher::Asset(const FString& PackagePath)
{
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
if (!Obj)
@@ -105,6 +91,17 @@ void MCPFetcher::LoadUAsset(const FString& PackagePath)
// If this is a material open in the editor, use the editor's transient copy.
if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
SetObj(MCPUtils::ReplaceMaterialWithTransientCopy(Mat));
return *this;
}
const MCPFetcher::FWalker* MCPFetcher::GetWalker(const FString& Key)
{
for (const FWalker& W : GetWalkerTable())
{
if (StrEq(Key, W.Key))
return &W;
}
return nullptr;
}
MCPFetcher& MCPFetcher::Graph(const FString& Value)

View File

@@ -72,6 +72,7 @@
#include "MaterialGraph/MaterialGraphNode.h"
#include "MaterialGraph/MaterialGraphSchema.h"
#include "IMaterialEditor.h"
#include "MaterialEditingLibrary.h"
#include "Subsystems/AssetEditorSubsystem.h"
// Mesh, animation, texture support
@@ -1125,6 +1126,8 @@ void MCPUtils::PreEdit(const TArray<UObject*>& Objects)
void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
{
TSet<UMaterial*> Materials;
TSet<UBlueprint*> Blueprints;
for (int32 i = Objects.Num() - 1; i >= 0; --i)
{
UObject* Obj = Objects[i];
@@ -1132,8 +1135,36 @@ void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
Obj->MarkPackageDirty();
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP);
Blueprints.Add(BP);
if (UMaterialInterface* MatIface = Cast<UMaterialInterface>(Obj))
if (UMaterial* BaseMat = MatIface->GetMaterial())
Materials.Add(BaseMat);
}
for (UMaterial *Material : Materials)
UMaterialEditingLibrary::RebuildMaterialInstanceEditors(Material);
for (UBlueprint *Blueprint : Blueprints)
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
if (GEditor)
GEditor->RedrawAllViewports();
}
TArray<MCPMaterialParameter> MCPUtils::MaterialParameters(UMaterialInstanceConstant *MI)
{
TArray<MCPMaterialParameter> Results;
UMaterial* BaseMat = MI->GetMaterial();
if (!BaseMat) return Results;
for (UMaterialExpression* Expr : BaseMat->GetExpressions())
{
if (!Expr || !Expr->bIsParameterExpression) continue;
MCPMaterialParameter P;
P.Name = Expr->GetParameterName();
if (P.Name.IsNone()) continue;
if (!Expr->GetParameterValue(P.Metadata)) continue;
Results.Add(P);
}
return Results;
}
bool MCPUtils::SaveMaterialPackage(UMaterial* Material)
@@ -1790,6 +1821,19 @@ bool MCPUtils::SetPropertyValueText(UObject* Container, FProperty* Prop, const F
return true;
}
bool MCPUtils::SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner, MCPErrorCallback Error)
{
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Owner, PPF_None);
if (!ImportResult)
{
Error.SetError(FString::Printf(TEXT("Failed to parse '%s' for property '%s' (type: %s)"),
*Value, *FormatName(Prop), *Prop->GetCPPType()));
return false;
}
return true;
}
// ============================================================
// SearchProperties
// ============================================================

View File

@@ -35,6 +35,9 @@ public:
MCPFetcher(MCPErrorCallback CB) : ErrorCB(CB) {}
MCPFetcher(MCPErrorCallback CB, UObject* O) : Obj(O), ErrorCB(CB) {}
// Starting point is always Asset.
MCPFetcher& Asset(const FString& PackagePath);
// Walk one step.
MCPFetcher& Graph(const FString& Value);
MCPFetcher& Node(const FString& Value);
@@ -43,13 +46,13 @@ public:
MCPFetcher& LevelBlueprint(const FString& Value);
MCPFetcher& MatExp(const FString& Value);
// Parse string and walk multiple steps.
MCPFetcher& Walk(const FString& Path);
// C++-only walk step: resolve to the editable template
// (e.g. Blueprint → CDO, everything else → as-is).
MCPFetcher& Template();
// Parse string and walk multiple steps.
MCPFetcher& Walk(const FString& Path);
// Walker table entry.
struct FWalker
{
@@ -74,6 +77,7 @@ public:
return Result;
}
private:
TArray<UObject*> Chain;
static bool StrEq(const FString& A, const TCHAR* B) { return A.Equals(B, ESearchCase::IgnoreCase); }
@@ -81,7 +85,7 @@ private:
void SetPin(UEdGraphPin* InPin) { ResultPin = InPin; Obj = nullptr; }
MCPFetcher& SetError(const FString& Msg);
MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected);
void LoadUAsset(const FString& PackagePath);
const FWalker* GetWalker(const FString& Key);
};
template<> inline UEdGraphPin* MCPFetcher::Cast<UEdGraphPin>()

View File

@@ -3,6 +3,8 @@
#include "CoreMinimal.h"
#include "Dom/JsonObject.h"
#include "EdGraph/EdGraphPin.h"
#include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h"
class UBlueprint;
class UEdGraph;
@@ -96,6 +98,12 @@ struct MCPErrorCallback
void SetError(const FString& Msg) const { Func(Msg); }
};
struct MCPMaterialParameter
{
FName Name;
FMaterialParameterMetadata Metadata;
};
// Stateless utility functions used by MCP handlers and the MCP server.
// This is effectively a namespace — all methods are static.
class MCPUtils
@@ -277,6 +285,9 @@ public:
static void PreEdit(const TArray<UObject*>& Objects);
static void PostEdit(const TArray<UObject*>& Objects);
// ----- Material Instance -----
static TArray<MCPMaterialParameter> MaterialParameters(UMaterialInstanceConstant *MI);
// ----- Editable template -----
// Given an object, returns the appropriate template object for generic
// property editing, or nullptr if the type isn't whitelisted.
@@ -287,6 +298,7 @@ public:
static FProperty* FindPropertyByName(UObject* Obj, const FString& Name, MCPErrorCallback Error = nullptr);
static FString GetPropertyValueText(UObject* Container, FProperty* Prop);
static bool SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value, MCPErrorCallback Error = nullptr);
static bool SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner, MCPErrorCallback Error = nullptr);
// ----- Property population -----
static FString PropertyNameToJsonKey(const FString& PropName);