#pragma once #include "CoreMinimal.h" #include "MCPHandler.h" #include "MCPAssets.h" #include "MCPUtils.h" #include "Materials/Material.h" #include "MaterialDomain.h" #include "Materials/MaterialInstanceConstant.h" #include "Materials/MaterialExpression.h" #include "Materials/MaterialExpressionScalarParameter.h" #include "Materials/MaterialExpressionVectorParameter.h" #include "Materials/MaterialExpressionTextureSampleParameter2D.h" #include "Materials/MaterialExpressionStaticSwitchParameter.h" #include "Materials/MaterialExpressionTextureSample.h" #include "Engine/Texture.h" #include "UMCPHandler_DumpMaterial.generated.h" // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- UCLASS(meta=(Group="Unclassified")) 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, FStringBuilderBase& Result) override { MCPAssets Assets; Assets.NoScans(); Assets.Scan(); Assets.Scan(); if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; UMaterialInterface* LoadedObj = Assets.Object(); if (UMaterial* Mat = Cast(LoadedObj)) { EmitMaterial(Mat, Result); return; } if (UMaterialInstanceConstant* MI = Cast(LoadedObj)) { EmitMaterialInstance(MI, Result); return; } Result.Appendf(TEXT("ERROR: Loaded object is %s, expected Material or MaterialInstance.\n"), *LoadedObj->GetClass()->GetName()); } private: void EmitMaterial(UMaterial* Mat, FStringBuilderBase& Result) { Result.Appendf(TEXT("Material: %s\n"), *MCPUtils::FormatName(Mat)); Result.Appendf(TEXT("Domain: %s\n"), *MCPUtils::EnumToString(Mat->MaterialDomain, TEXT("MD_"))); Result.Appendf(TEXT("BlendMode: %s\n"), *MCPUtils::EnumToString(Mat->BlendMode, TEXT("BLEND_"))); Result.Appendf(TEXT("TwoSided: %s\n"), Mat->IsTwoSided() ? TEXT("true") : TEXT("false")); // Shading models FMaterialShadingModelField SMField = Mat->GetShadingModels(); const UEnum* SMEnum = StaticEnum(); TArray SMNames; for (int32 i = 0; i < SMEnum->NumEnums() - 1; ++i) { EMaterialShadingModel SM = (EMaterialShadingModel)SMEnum->GetValueByIndex(i); if (SMField.HasShadingModel(SM)) SMNames.Add(SMEnum->GetNameStringByIndex(i)); } Result.Appendf(TEXT("ShadingModels: %s\n"), *FString::Join(SMNames, TEXT(", "))); // Parameters auto Expressions = Mat->GetExpressions(); bool bHasParams = false; for (UMaterialExpression* Expr : Expressions) { if (!Expr) continue; if (auto* SP = Cast(Expr)) { if (!bHasParams) { Result.Append(TEXT("\nParameters:\n")); bHasParams = true; } Result.Appendf(TEXT(" Scalar \"%s\" = %g"), *SP->ParameterName.ToString(), SP->DefaultValue); if (!SP->Group.IsNone()) Result.Appendf(TEXT(" [%s]"), *SP->Group.ToString()); Result.Append(TEXT("\n")); } else if (auto* VP = Cast(Expr)) { if (!bHasParams) { Result.Append(TEXT("\nParameters:\n")); bHasParams = true; } Result.Appendf(TEXT(" Vector \"%s\" = (%.3f, %.3f, %.3f, %.3f)"), *VP->ParameterName.ToString(), VP->DefaultValue.R, VP->DefaultValue.G, VP->DefaultValue.B, VP->DefaultValue.A); if (!VP->Group.IsNone()) Result.Appendf(TEXT(" [%s]"), *VP->Group.ToString()); Result.Append(TEXT("\n")); } else if (auto* TP = Cast(Expr)) { if (!bHasParams) { Result.Append(TEXT("\nParameters:\n")); bHasParams = true; } Result.Appendf(TEXT(" Texture \"%s\" = %s"), *TP->ParameterName.ToString(), TP->Texture ? *MCPUtils::FormatName(TP->Texture) : TEXT("None")); if (!TP->Group.IsNone()) Result.Appendf(TEXT(" [%s]"), *TP->Group.ToString()); Result.Append(TEXT("\n")); } else if (auto* SSP = Cast(Expr)) { if (!bHasParams) { Result.Append(TEXT("\nParameters:\n")); bHasParams = true; } Result.Appendf(TEXT(" StaticSwitch \"%s\" = %s"), *SSP->ParameterName.ToString(), SSP->DefaultValue ? TEXT("true") : TEXT("false")); if (!SSP->Group.IsNone()) Result.Appendf(TEXT(" [%s]"), *SSP->Group.ToString()); Result.Append(TEXT("\n")); } } // Referenced textures auto RefTexObjs = Mat->GetReferencedTextures(); bool bHasTextures = false; for (const TObjectPtr& TexObj : RefTexObjs) { if (!TexObj) continue; if (!bHasTextures) { Result.Append(TEXT("\nReferenced Textures:\n")); bHasTextures = true; } if (UTexture* Tex = Cast(TexObj.Get())) Result.Appendf(TEXT(" %s\n"), *MCPUtils::FormatName(Tex)); else Result.Appendf(TEXT(" %s\n"), *TexObj->GetPathName()); } // Usage flags — only print enabled ones Result.Append(TEXT("\nUsage Flags:")); bool bAnyUsage = false; auto EmitFlag = [&](bool bSet, const TCHAR* Name) { if (bSet) { Result.Appendf(TEXT(" %s"), Name); bAnyUsage = true; } }; EmitFlag(Mat->bUsedWithSkeletalMesh, TEXT("SkeletalMesh")); EmitFlag(Mat->bUsedWithMorphTargets, TEXT("MorphTargets")); EmitFlag(Mat->bUsedWithNiagaraSprites, TEXT("NiagaraSprites")); EmitFlag(Mat->bUsedWithParticleSprites, TEXT("ParticleSprites")); EmitFlag(Mat->bUsedWithStaticLighting, TEXT("StaticLighting")); if (!bAnyUsage) Result.Append(TEXT(" (none)")); Result.Append(TEXT("\n")); // Stats Result.Appendf(TEXT("Expressions: %d\n"), Expressions.Num()); int32 TextureSampleCount = 0; for (UMaterialExpression* Expr : Expressions) if (Expr && Expr->IsA()) TextureSampleCount++; Result.Appendf(TEXT("TextureSamples: %d\n"), TextureSampleCount); if (Mat->MaterialGraph) Result.Appendf(TEXT("GraphNodes: %d\n"), Mat->MaterialGraph->Nodes.Num()); // Additional settings — only print non-default values if (Mat->OpacityMaskClipValue != 0.3333f) Result.Appendf(TEXT("OpacityMaskClipValue: %g\n"), Mat->OpacityMaskClipValue); if (Mat->DitheredLODTransition) Result.Append(TEXT("DitheredLODTransition: true\n")); if (Mat->bAllowNegativeEmissiveColor) Result.Append(TEXT("AllowNegativeEmissiveColor: true\n")); } void EmitMaterialInstance(UMaterialInstanceConstant* MI, FStringBuilderBase& Result) { Result.Appendf(TEXT("MaterialInstance: %s\n"), *MCPUtils::FormatName(MI)); if (MI->Parent) { if (UMaterial* ParentMat = Cast(MI->Parent)) Result.Appendf(TEXT("Parent: %s\n"), *MCPUtils::FormatName(ParentMat)); else if (UMaterialInstance* ParentMI = Cast(MI->Parent)) Result.Appendf(TEXT("Parent: %s\n"), *MCPUtils::FormatName(ParentMI)); else Result.Appendf(TEXT("Parent: %s\n"), *MI->Parent->GetPathName()); } // Overridden parameters bool bHasParams = false; auto EnsureHeader = [&]() { if (!bHasParams) { Result.Append(TEXT("\nOverridden Parameters:\n")); bHasParams = true; } }; for (const FScalarParameterValue& P : MI->ScalarParameterValues) { EnsureHeader(); Result.Appendf(TEXT(" Scalar \"%s\" = %g\n"), *P.ParameterInfo.Name.ToString(), P.ParameterValue); } for (const FVectorParameterValue& P : MI->VectorParameterValues) { EnsureHeader(); Result.Appendf(TEXT(" Vector \"%s\" = (%.3f, %.3f, %.3f, %.3f)\n"), *P.ParameterInfo.Name.ToString(), P.ParameterValue.R, P.ParameterValue.G, P.ParameterValue.B, P.ParameterValue.A); } for (const FTextureParameterValue& P : MI->TextureParameterValues) { EnsureHeader(); if (P.ParameterValue) { Result.Appendf(TEXT(" Texture \"%s\" = %s\n"), *P.ParameterInfo.Name.ToString(), *MCPUtils::FormatName(P.ParameterValue)); } else { Result.Appendf(TEXT(" Texture \"%s\" = None\n"), *P.ParameterInfo.Name.ToString()); } } for (const FStaticSwitchParameter& P : MI->GetStaticParameters().StaticSwitchParameters) { EnsureHeader(); Result.Appendf(TEXT(" StaticSwitch \"%s\" = %s%s\n"), *P.ParameterInfo.Name.ToString(), P.Value ? TEXT("true") : TEXT("false"), P.bOverride ? TEXT("") : TEXT(" (not overridden)")); } } };