Working on MI SetParameter
This commit is contained in:
BIN
Content/Testing/M_Test.uasset
LFS
BIN
Content/Testing/M_Test.uasset
LFS
Binary file not shown.
BIN
Content/Testing/M_Test_Inst.uasset
LFS
Normal file
BIN
Content/Testing/M_Test_Inst.uasset
LFS
Normal file
Binary file not shown.
BIN
Content/Testing/M_Test_Inst_Inst.uasset
LFS
Normal file
BIN
Content/Testing/M_Test_Inst_Inst.uasset
LFS
Normal file
Binary file not shown.
@@ -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": {
|
||||
|
||||
@@ -29,7 +29,9 @@ public class BlueprintMCP : ModuleRules
|
||||
"MaterialEditor",
|
||||
"AnimGraph",
|
||||
"AnimGraphRuntime",
|
||||
"RHI"
|
||||
"RHI",
|
||||
"Slate",
|
||||
"SlateCore"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
// ============================================================
|
||||
|
||||
@@ -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>()
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user