#pragma once #include "CoreMinimal.h" #include "MCPHandler.h" #include "MCPAssets.h" #include "MCPUtils.h" #include "Materials/Material.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/MaterialExpressionMaterialFunctionCall.h" #include "Materials/MaterialFunction.h" #include "MaterialGraph/MaterialGraph.h" #include "MaterialGraph/MaterialGraphNode.h" #include "MaterialGraph/MaterialGraphNode_Root.h" #include "MaterialGraph/MaterialGraphSchema.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "UMCPHandler_DescribeMaterialInEnglish.generated.h" // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- // --------------------------------------------------------------------------- UCLASS(meta=(Group="Unclassified")) class UMCPHandler_DescribeMaterialInEnglish : public UObject, public IMCPHandler { GENERATED_BODY() public: UPROPERTY(meta=(Description="Material name or package path")) FString Material; virtual FString GetDescription() const override { return TEXT("Generate a human-readable description of a material by tracing its expression graph from the root node inputs."); } virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override { MCPAssets Assets; if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; UMaterial* MaterialObj = Assets.Object(); // Ensure material graph is built MCPUtils::EnsureMaterialGraph(MaterialObj); if (!MaterialObj->MaterialGraph) { MCPErrorCallback(Result).SetError(TEXT("Could not build MaterialGraph for this material")); return; } // Find root node UMaterialGraphNode_Root* RootNode = nullptr; for (UEdGraphNode* Node : MaterialObj->MaterialGraph->Nodes) { RootNode = Cast(Node); if (RootNode) break; } if (!RootNode) { MCPErrorCallback(Result).SetError(TEXT("Could not find root node in material graph")); return; } // Recursive helper: trace backwards from a pin and build a description string TFunction TracePin = [&TracePin](UEdGraphPin* Pin, int32 Depth) -> FString { if (!Pin || Depth > 10) return TEXT("(unknown)"); if (Pin->LinkedTo.Num() == 0) { if (!Pin->DefaultValue.IsEmpty()) return FString::Printf(TEXT("(default: %s)"), *Pin->DefaultValue); return TEXT("(unconnected)"); } TArray Sources; for (UEdGraphPin* LinkedPin : Pin->LinkedTo) { if (!LinkedPin || !LinkedPin->GetOwningNode()) continue; UEdGraphNode* SourceNode = LinkedPin->GetOwningNode(); FString NodeDesc; UMaterialGraphNode* MatNode = Cast(SourceNode); if (!MatNode) { NodeDesc = MCPUtils::FormatName(SourceNode); Sources.Add(NodeDesc); continue; } UMaterialExpression* Expr = MatNode->MaterialExpression; if (!Expr) { NodeDesc = TEXT("(null expression)"); } else if (auto* SP = Cast(Expr)) { NodeDesc = FString::Printf(TEXT("ScalarParam \"%s\" (default: %.4f)"), *SP->ParameterName.ToString(), SP->DefaultValue); } else if (auto* VP = Cast(Expr)) { NodeDesc = FString::Printf(TEXT("VectorParam \"%s\" (default: R=%.2f G=%.2f B=%.2f A=%.2f)"), *VP->ParameterName.ToString(), VP->DefaultValue.R, VP->DefaultValue.G, VP->DefaultValue.B, VP->DefaultValue.A); } else if (auto* TP = Cast(Expr)) { FString TexName = TP->Texture ? MCPUtils::FormatName(TP->Texture) : TEXT("None"); NodeDesc = FString::Printf(TEXT("TextureParam \"%s\" (%s)"), *TP->ParameterName.ToString(), *TexName); } else if (auto* SSP = Cast(Expr)) { NodeDesc = FString::Printf(TEXT("StaticSwitchParam \"%s\" (default: %s)"), *SSP->ParameterName.ToString(), SSP->DefaultValue ? TEXT("true") : TEXT("false")); } else if (auto* SC = Cast(Expr)) { NodeDesc = FString::Printf(TEXT("Constant(%.4f)"), SC->R); } else if (auto* C3 = Cast(Expr)) { NodeDesc = FString::Printf(TEXT("Constant3(R=%.2f G=%.2f B=%.2f)"), C3->Constant.R, C3->Constant.G, C3->Constant.B); } else if (auto* C4 = Cast(Expr)) { NodeDesc = FString::Printf(TEXT("Constant4(R=%.2f G=%.2f B=%.2f A=%.2f)"), C4->Constant.R, C4->Constant.G, C4->Constant.B, C4->Constant.A); } else if (auto* TS = Cast(Expr)) { FString TexName = TS->Texture ? MCPUtils::FormatName(TS->Texture) : TEXT("None"); NodeDesc = FString::Printf(TEXT("TextureSample(%s)"), *TexName); } else if (auto* MFC = Cast(Expr)) { FString FuncName = MFC->MaterialFunction ? MFC->MaterialFunction->GetPathName() : TEXT("None"); NodeDesc = FString::Printf(TEXT("FunctionCall(%s)"), *FuncName); } else { NodeDesc = MCPUtils::FormatName(Expr); } // Recurse into input pins TArray InputDescs; for (UEdGraphPin* InputPin : SourceNode->Pins) { if (!InputPin || InputPin->Direction != EGPD_Input || InputPin->LinkedTo.Num() == 0) continue; InputDescs.Add(TracePin(InputPin, Depth + 1)); } if (InputDescs.Num() > 0) { NodeDesc += TEXT(" <- (") + FString::Join(InputDescs, TEXT(", ")) + TEXT(")"); } Sources.Add(NodeDesc); } if (Sources.Num() == 1) return Sources[0]; return TEXT("(") + FString::Join(Sources, TEXT(", ")) + TEXT(")"); }; // Trace each connected root input and output plain text Result.Appendf(TEXT("Material: %s\n\n"), *MCPUtils::FormatName(MaterialObj)); for (UEdGraphPin* Pin : RootNode->Pins) { if (!Pin || Pin->Direction != EGPD_Input) continue; if (Pin->LinkedTo.Num() == 0) continue; FString PinName = MCPUtils::FormatName(Pin); FString Description = TracePin(Pin, 0); Result.Appendf(TEXT("%s <- %s\n"), *PinName, *Description); } } };