189 lines
6.6 KiB
C++
189 lines
6.6 KiB
C++
#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<UMaterial> 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<UMaterialGraphNode_Root>(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<FString(UEdGraphPin*, int32)> 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<FString> Sources;
|
|
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
|
{
|
|
if (!LinkedPin || !LinkedPin->GetOwningNode()) continue;
|
|
|
|
UEdGraphNode* SourceNode = LinkedPin->GetOwningNode();
|
|
FString NodeDesc;
|
|
|
|
UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(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<UMaterialExpressionScalarParameter>(Expr))
|
|
{
|
|
NodeDesc = FString::Printf(TEXT("ScalarParam \"%s\" (default: %.4f)"), *SP->ParameterName.ToString(), SP->DefaultValue);
|
|
}
|
|
else if (auto* VP = Cast<UMaterialExpressionVectorParameter>(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<UMaterialExpressionTextureSampleParameter2D>(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<UMaterialExpressionStaticSwitchParameter>(Expr))
|
|
{
|
|
NodeDesc = FString::Printf(TEXT("StaticSwitchParam \"%s\" (default: %s)"),
|
|
*SSP->ParameterName.ToString(), SSP->DefaultValue ? TEXT("true") : TEXT("false"));
|
|
}
|
|
else if (auto* SC = Cast<UMaterialExpressionConstant>(Expr))
|
|
{
|
|
NodeDesc = FString::Printf(TEXT("Constant(%.4f)"), SC->R);
|
|
}
|
|
else if (auto* C3 = Cast<UMaterialExpressionConstant3Vector>(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<UMaterialExpressionConstant4Vector>(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<UMaterialExpressionTextureSample>(Expr))
|
|
{
|
|
FString TexName = TS->Texture ? MCPUtils::FormatName(TS->Texture) : TEXT("None");
|
|
NodeDesc = FString::Printf(TEXT("TextureSample(%s)"), *TexName);
|
|
}
|
|
else if (auto* MFC = Cast<UMaterialExpressionMaterialFunctionCall>(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<FString> 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);
|
|
}
|
|
}
|
|
};
|