Files
integration/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/Handlers/UMCPHandler_DumpMaterial.h
2026-03-08 22:17:14 -04:00

294 lines
12 KiB
C++

#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPAssetFinder.h"
#include "MCPUtils.h"
#include "Materials/Material.h"
#include "MaterialDomain.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Materials/MaterialFunction.h"
#include "Materials/MaterialExpression.h"
#include "Materials/MaterialExpressionScalarParameter.h"
#include "Materials/MaterialExpressionVectorParameter.h"
#include "Materials/MaterialExpressionTextureObjectParameter.h"
#include "Materials/MaterialExpressionTextureSampleParameter2D.h"
#include "Materials/MaterialExpressionStaticSwitchParameter.h"
#include "Materials/MaterialExpressionConstant.h"
#include "Materials/MaterialExpressionConstant3Vector.h"
#include "Materials/MaterialExpressionConstant4Vector.h"
#include "Materials/MaterialExpressionTextureSample.h"
#include "Materials/MaterialExpressionTextureCoordinate.h"
#include "Materials/MaterialExpressionComponentMask.h"
#include "Materials/MaterialExpressionCustom.h"
#include "Materials/MaterialExpressionFunctionInput.h"
#include "Materials/MaterialExpressionFunctionOutput.h"
#include "Materials/MaterialExpressionMaterialFunctionCall.h"
#include "MaterialGraph/MaterialGraph.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "MaterialGraph/MaterialGraphNode_Root.h"
#include "MaterialGraph/MaterialGraphSchema.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "UMCPHandler_DumpMaterial.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCPHandler_DumpMaterial : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Material or MaterialInstance name or package path"))
FString Material;
virtual FString GetDescription() const override
{
return TEXT("Get detailed info about a material or material instance, including parameters, usage flags, and referenced textures.");
}
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
{
FString DecodedName = MCPUtils::UrlDecode(Material);
// Try loading as UMaterial or UMaterialInstanceConstant
MCPAssets<UMaterialInterface> Assets;
Assets.Scan(UMaterial::StaticClass());
Assets.Scan(UMaterialInstanceConstant::StaticClass());
if (!Assets.Exact(DecodedName).Errors(Result).ENone().ETwo().Load()) return;
UMaterialInterface* LoadedObj = Assets.Object();
if (UMaterial* MaterialObj = Cast<UMaterial>(LoadedObj))
{
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material '%s'"), *MaterialObj->GetName());
Result->SetStringField(TEXT("name"), MaterialObj->GetName());
Result->SetStringField(TEXT("path"), MaterialObj->GetPathName());
Result->SetStringField(TEXT("type"), TEXT("Material"));
// Material domain
FString DomainStr = TEXT("Unknown");
if (const UEnum* DomainEnum = StaticEnum<EMaterialDomain>())
{
DomainStr = DomainEnum->GetNameStringByValue((int64)MaterialObj->MaterialDomain);
}
Result->SetStringField(TEXT("domain"), DomainStr);
// Blend mode
FString BlendModeStr = TEXT("Unknown");
if (const UEnum* BlendEnum = StaticEnum<EBlendMode>())
{
BlendModeStr = BlendEnum->GetNameStringByValue((int64)MaterialObj->BlendMode);
}
Result->SetStringField(TEXT("blendMode"), BlendModeStr);
// Shading models
TArray<TSharedPtr<FJsonValue>> ShadingModels;
FMaterialShadingModelField SMField = MaterialObj->GetShadingModels();
if (const UEnum* SMEnum = StaticEnum<EMaterialShadingModel>())
{
for (int32 i = 0; i < SMEnum->NumEnums() - 1; ++i)
{
EMaterialShadingModel SM = (EMaterialShadingModel)SMEnum->GetValueByIndex(i);
if (SMField.HasShadingModel(SM))
{
ShadingModels.Add(MakeShared<FJsonValueString>(SMEnum->GetNameStringByIndex(i)));
}
}
}
Result->SetArrayField(TEXT("shadingModels"), ShadingModels);
// Two-sided
Result->SetBoolField(TEXT("twoSided"), MaterialObj->IsTwoSided());
// Expression count
auto Expressions = MaterialObj->GetExpressions();
Result->SetNumberField(TEXT("expressionCount"), Expressions.Num());
// Parameters — iterate expressions for parameter types
TArray<TSharedPtr<FJsonValue>> Parameters;
for (UMaterialExpression* Expr : Expressions)
{
if (!Expr) continue;
TSharedRef<FJsonObject> ParamObj = MakeShared<FJsonObject>();
bool bIsParam = false;
if (auto* SP = Cast<UMaterialExpressionScalarParameter>(Expr))
{
bIsParam = true;
ParamObj->SetStringField(TEXT("name"), SP->ParameterName.ToString());
ParamObj->SetStringField(TEXT("type"), TEXT("Scalar"));
ParamObj->SetStringField(TEXT("group"), SP->Group.ToString());
ParamObj->SetNumberField(TEXT("defaultValue"), SP->DefaultValue);
}
else if (auto* VP = Cast<UMaterialExpressionVectorParameter>(Expr))
{
bIsParam = true;
ParamObj->SetStringField(TEXT("name"), VP->ParameterName.ToString());
ParamObj->SetStringField(TEXT("type"), TEXT("Vector"));
ParamObj->SetStringField(TEXT("group"), VP->Group.ToString());
TSharedRef<FJsonObject> DefVal = MakeShared<FJsonObject>();
DefVal->SetNumberField(TEXT("r"), VP->DefaultValue.R);
DefVal->SetNumberField(TEXT("g"), VP->DefaultValue.G);
DefVal->SetNumberField(TEXT("b"), VP->DefaultValue.B);
DefVal->SetNumberField(TEXT("a"), VP->DefaultValue.A);
ParamObj->SetObjectField(TEXT("defaultValue"), DefVal);
}
else if (auto* TP = Cast<UMaterialExpressionTextureSampleParameter2D>(Expr))
{
bIsParam = true;
ParamObj->SetStringField(TEXT("name"), TP->ParameterName.ToString());
ParamObj->SetStringField(TEXT("type"), TEXT("Texture"));
ParamObj->SetStringField(TEXT("group"), TP->Group.ToString());
if (TP->Texture)
ParamObj->SetStringField(TEXT("defaultValue"), TP->Texture->GetPathName());
}
else if (auto* SSP = Cast<UMaterialExpressionStaticSwitchParameter>(Expr))
{
bIsParam = true;
ParamObj->SetStringField(TEXT("name"), SSP->ParameterName.ToString());
ParamObj->SetStringField(TEXT("type"), TEXT("StaticSwitch"));
ParamObj->SetStringField(TEXT("group"), SSP->Group.ToString());
ParamObj->SetBoolField(TEXT("defaultValue"), SSP->DefaultValue);
}
if (bIsParam)
{
Parameters.Add(MakeShared<FJsonValueObject>(ParamObj));
}
}
Result->SetArrayField(TEXT("parameters"), Parameters);
// Referenced textures
TArray<TSharedPtr<FJsonValue>> ReferencedTextures;
auto RefTexObjs = MaterialObj->GetReferencedTextures();
for (const TObjectPtr<UObject>& TexObj : RefTexObjs)
{
if (TexObj)
{
ReferencedTextures.Add(MakeShared<FJsonValueString>(TexObj->GetPathName()));
}
}
Result->SetArrayField(TEXT("referencedTextures"), ReferencedTextures);
// Graph node count
int32 GraphNodeCount = 0;
if (MaterialObj->MaterialGraph)
{
GraphNodeCount = MaterialObj->MaterialGraph->Nodes.Num();
}
Result->SetNumberField(TEXT("graphNodeCount"), GraphNodeCount);
// Usage flags
TSharedRef<FJsonObject> UsageFlags = MakeShared<FJsonObject>();
UsageFlags->SetBoolField(TEXT("bUsedWithSkeletalMesh"), MaterialObj->bUsedWithSkeletalMesh != 0);
UsageFlags->SetBoolField(TEXT("bUsedWithMorphTargets"), MaterialObj->bUsedWithMorphTargets != 0);
UsageFlags->SetBoolField(TEXT("bUsedWithNiagaraSprites"), MaterialObj->bUsedWithNiagaraSprites != 0);
UsageFlags->SetBoolField(TEXT("bUsedWithParticleSprites"), MaterialObj->bUsedWithParticleSprites != 0);
UsageFlags->SetBoolField(TEXT("bUsedWithStaticLighting"), MaterialObj->bUsedWithStaticLighting != 0);
Result->SetObjectField(TEXT("usageFlags"), UsageFlags);
// Opacity mask clip value
Result->SetNumberField(TEXT("opacityMaskClipValue"), MaterialObj->OpacityMaskClipValue);
// Additional settings
Result->SetBoolField(TEXT("ditheredLODTransition"), MaterialObj->DitheredLODTransition != 0);
Result->SetBoolField(TEXT("bAllowNegativeEmissiveColor"), MaterialObj->bAllowNegativeEmissiveColor != 0);
// Texture sample count (simple expression scan)
int32 TextureSampleCount = 0;
for (UMaterialExpression* Expr : Expressions)
{
if (Expr && Expr->IsA<UMaterialExpressionTextureSample>())
{
TextureSampleCount++;
}
}
Result->SetNumberField(TEXT("textureSampleCount"), TextureSampleCount);
return;
}
if (UMaterialInstanceConstant* MI = Cast<UMaterialInstanceConstant>(LoadedObj))
{
UE_LOG(LogTemp, Display, TEXT("BlueprintMCP: GetMaterial — loaded material instance '%s'"), *MI->GetName());
Result->SetStringField(TEXT("name"), MI->GetName());
Result->SetStringField(TEXT("path"), MI->GetPathName());
Result->SetStringField(TEXT("type"), TEXT("MaterialInstance"));
if (MI->Parent)
{
Result->SetStringField(TEXT("parent"), MI->Parent->GetName());
Result->SetStringField(TEXT("parentPath"), MI->Parent->GetPathName());
}
// Overridden parameters
TArray<TSharedPtr<FJsonValue>> OverriddenParams;
// Scalar parameters
for (const FScalarParameterValue& Param : MI->ScalarParameterValues)
{
TSharedRef<FJsonObject> PObj = MakeShared<FJsonObject>();
PObj->SetStringField(TEXT("name"), Param.ParameterInfo.Name.ToString());
PObj->SetStringField(TEXT("type"), TEXT("Scalar"));
PObj->SetNumberField(TEXT("value"), Param.ParameterValue);
OverriddenParams.Add(MakeShared<FJsonValueObject>(PObj));
}
// Vector parameters
for (const FVectorParameterValue& Param : MI->VectorParameterValues)
{
TSharedRef<FJsonObject> PObj = MakeShared<FJsonObject>();
PObj->SetStringField(TEXT("name"), Param.ParameterInfo.Name.ToString());
PObj->SetStringField(TEXT("type"), TEXT("Vector"));
TSharedRef<FJsonObject> Val = MakeShared<FJsonObject>();
Val->SetNumberField(TEXT("r"), Param.ParameterValue.R);
Val->SetNumberField(TEXT("g"), Param.ParameterValue.G);
Val->SetNumberField(TEXT("b"), Param.ParameterValue.B);
Val->SetNumberField(TEXT("a"), Param.ParameterValue.A);
PObj->SetObjectField(TEXT("value"), Val);
OverriddenParams.Add(MakeShared<FJsonValueObject>(PObj));
}
// Texture parameters
for (const FTextureParameterValue& Param : MI->TextureParameterValues)
{
TSharedRef<FJsonObject> PObj = MakeShared<FJsonObject>();
PObj->SetStringField(TEXT("name"), Param.ParameterInfo.Name.ToString());
PObj->SetStringField(TEXT("type"), TEXT("Texture"));
if (Param.ParameterValue)
PObj->SetStringField(TEXT("value"), Param.ParameterValue->GetPathName());
else
PObj->SetStringField(TEXT("value"), TEXT("None"));
OverriddenParams.Add(MakeShared<FJsonValueObject>(PObj));
}
// Static switch parameters
for (const FStaticSwitchParameter& Param : MI->GetStaticParameters().StaticSwitchParameters)
{
TSharedRef<FJsonObject> PObj = MakeShared<FJsonObject>();
PObj->SetStringField(TEXT("name"), Param.ParameterInfo.Name.ToString());
PObj->SetStringField(TEXT("type"), TEXT("StaticSwitch"));
PObj->SetBoolField(TEXT("value"), Param.Value);
PObj->SetBoolField(TEXT("overridden"), Param.bOverride);
OverriddenParams.Add(MakeShared<FJsonValueObject>(PObj));
}
Result->SetArrayField(TEXT("overriddenParameters"), OverriddenParams);
return;
}
MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Material or MaterialInstance '%s' not found. Use list_materials to see available assets."), *DecodedName));
}
};