Files
integration/Plugins/UEWingman/Deprecated/UMCPHandler_SetMaterialExpressionProperty.h

283 lines
9.5 KiB
C
Raw Normal View History

2026-03-08 22:17:14 -04:00
#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
2026-03-13 14:26:04 -04:00
#include "MCPAssets.h"
2026-03-10 07:17:42 -04:00
#include "MCPFetcher.h"
2026-03-08 22:17:14 -04:00
#include "MCPUtils.h"
#include "Materials/Material.h"
#include "Materials/MaterialFunction.h"
#include "Materials/MaterialExpression.h"
#include "Materials/MaterialExpressionScalarParameter.h"
#include "Materials/MaterialExpressionVectorParameter.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 "MaterialGraph/MaterialGraph.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "UMCPHandler_SetMaterialExpressionProperty.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
2026-03-11 22:03:32 -04:00
UCLASS(meta=(Group="Unclassified"))
2026-03-08 22:17:14 -04:00
class UMCPHandler_SetMaterialExpressionProperty : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Material name or package path (specify this or materialFunction)"))
FString Material;
UPROPERTY(meta=(Optional, Description="Material function name or package path (specify this or material)"))
FString MaterialFunction;
2026-03-10 07:17:42 -04:00
UPROPERTY(meta=(Description="Expression node name (from FormatName)"))
2026-03-08 22:17:14 -04:00
FString Node;
virtual FString GetDescription() const override
{
return TEXT("Set the value or properties on a material expression node. "
"The 'value' field in the JSON payload provides the new value, whose format depends on the expression type.");
}
2026-03-10 07:17:42 -04:00
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
2026-03-08 22:17:14 -04:00
{
if (Material.IsEmpty() && MaterialFunction.IsEmpty())
{
2026-03-10 07:17:42 -04:00
Result.Append(TEXT("ERROR: Specify 'material' or 'materialFunction'\n"));
return;
2026-03-08 22:17:14 -04:00
}
if (!Json->HasField(TEXT("value")))
{
2026-03-10 07:17:42 -04:00
Result.Append(TEXT("ERROR: Missing required field: value\n"));
return;
2026-03-08 22:17:14 -04:00
}
// Load material or material function
UMaterial* MaterialObj = nullptr;
UMaterialFunction* MatFunc = nullptr;
if (!MaterialFunction.IsEmpty())
{
MCPAssets<UMaterialFunction> MFAssets;
if (!MFAssets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return;
MatFunc = MFAssets.Object();
}
else
{
MCPAssets<UMaterial> MatAssets;
if (!MatAssets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
MaterialObj = MatAssets.Object();
}
if (MaterialObj) MCPUtils::EnsureMaterialGraph(MaterialObj);
UEdGraph* Graph = MaterialObj ? (UEdGraph*)MaterialObj->MaterialGraph : (MatFunc ? MatFunc->MaterialGraph : nullptr);
if (!Graph)
{
2026-03-10 07:17:42 -04:00
Result.Appendf(TEXT("ERROR: No material graph found\n"));
return;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
// Find the node by name using Identifies
2026-03-08 22:17:14 -04:00
UMaterialGraphNode* TargetMatNode = nullptr;
for (UEdGraphNode* GraphNode : Graph->Nodes)
{
if (!GraphNode) continue;
2026-03-10 07:17:42 -04:00
if (!MCPUtils::Identifies(Node, GraphNode)) continue;
TargetMatNode = Cast<UMaterialGraphNode>(GraphNode);
if (TargetMatNode) break;
2026-03-08 22:17:14 -04:00
}
if (!TargetMatNode)
{
2026-03-10 07:17:42 -04:00
Result.Appendf(TEXT("ERROR: Node '%s' not found in material graph\n"), *Node);
return;
2026-03-08 22:17:14 -04:00
}
UMaterialExpression* Expr = TargetMatNode->MaterialExpression;
if (!Expr)
{
2026-03-10 07:17:42 -04:00
Result.Appendf(TEXT("ERROR: Node '%s' has no material expression\n"), *MCPUtils::FormatName(TargetMatNode));
return;
2026-03-08 22:17:14 -04:00
}
UObject* Asset = MaterialObj ? (UObject*)MaterialObj : (UObject*)MatFunc;
Asset->PreEditChange(nullptr);
2026-03-10 07:17:42 -04:00
FString SetResult;
if (!ApplyValue(Expr, Json, Result, SetResult))
{
Asset->PostEditChange();
return;
}
Asset->PostEditChange();
Asset->MarkPackageDirty();
bool bSaved = MaterialObj ? MCPUtils::SaveMaterialPackage(MaterialObj) : MCPUtils::SaveGenericPackage(MatFunc);
Result.Appendf(TEXT("%s = %s"), *MCPUtils::FormatName(Expr), *SetResult);
if (!bSaved) Result.Append(TEXT(" (save failed)"));
Result.Append(TEXT("\n"));
}
private:
// Apply the value from JSON to the expression. Returns false on error (with message in Result).
// On success, fills SetResult with a human-readable summary of the new value.
bool ApplyValue(UMaterialExpression* Expr, const FJsonObject* Json, FStringBuilderBase& Result, FString& SetResult)
{
if (auto* E = Cast<UMaterialExpressionConstant>(Expr))
2026-03-08 22:17:14 -04:00
{
double Value = Json->GetNumberField(TEXT("value"));
2026-03-10 07:17:42 -04:00
E->R = (float)Value;
SetResult = FString::Printf(TEXT("%g"), Value);
return true;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
if (auto* E = Cast<UMaterialExpressionConstant3Vector>(Expr))
2026-03-08 22:17:14 -04:00
{
2026-03-10 07:17:42 -04:00
FLinearColor C;
if (!ParseColorValue(Json, C, false, Result)) return false;
E->Constant = C;
SetResult = FString::Printf(TEXT("(%.3f, %.3f, %.3f)"), C.R, C.G, C.B);
return true;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
if (auto* E = Cast<UMaterialExpressionConstant4Vector>(Expr))
2026-03-08 22:17:14 -04:00
{
2026-03-10 07:17:42 -04:00
FLinearColor C;
if (!ParseColorValue(Json, C, true, Result)) return false;
E->Constant = C;
SetResult = FString::Printf(TEXT("(%.3f, %.3f, %.3f, %.3f)"), C.R, C.G, C.B, C.A);
return true;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
if (auto* E = Cast<UMaterialExpressionScalarParameter>(Expr))
2026-03-08 22:17:14 -04:00
{
double Value = Json->GetNumberField(TEXT("value"));
2026-03-10 07:17:42 -04:00
E->DefaultValue = (float)Value;
SetResult = FString::Printf(TEXT("%g"), Value);
ApplyParameterName(E, Json);
return true;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
if (auto* E = Cast<UMaterialExpressionVectorParameter>(Expr))
{
FLinearColor C;
if (!ParseColorValue(Json, C, true, Result)) return false;
E->DefaultValue = C;
SetResult = FString::Printf(TEXT("(%.3f, %.3f, %.3f, %.3f)"), C.R, C.G, C.B, C.A);
ApplyParameterName(E, Json);
return true;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
if (auto* E = Cast<UMaterialExpressionTextureCoordinate>(Expr))
2026-03-08 22:17:14 -04:00
{
const TSharedPtr<FJsonObject>* ValueObj = nullptr;
2026-03-10 07:17:42 -04:00
if (!Json->TryGetObjectField(TEXT("value"), ValueObj) || !ValueObj || !(*ValueObj).IsValid())
2026-03-08 22:17:14 -04:00
{
2026-03-10 07:17:42 -04:00
Result.Append(TEXT("ERROR: TextureCoordinate requires value as {coordinateIndex, uTiling, vTiling}\n"));
return false;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
double CoordIndex = 0, UTiling = 1, VTiling = 1;
(*ValueObj)->TryGetNumberField(TEXT("coordinateIndex"), CoordIndex);
(*ValueObj)->TryGetNumberField(TEXT("uTiling"), UTiling);
(*ValueObj)->TryGetNumberField(TEXT("vTiling"), VTiling);
E->CoordinateIndex = (int32)CoordIndex;
E->UTiling = (float)UTiling;
E->VTiling = (float)VTiling;
SetResult = FString::Printf(TEXT("index=%d uTiling=%g vTiling=%g"), (int32)CoordIndex, UTiling, VTiling);
return true;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
if (auto* E = Cast<UMaterialExpressionCustom>(Expr))
2026-03-08 22:17:14 -04:00
{
FString Code;
if (Json->TryGetStringField(TEXT("code"), Code))
{
2026-03-10 07:17:42 -04:00
E->Code = Code;
2026-03-08 22:17:14 -04:00
}
else if (Json->HasField(TEXT("value")))
{
FString ValueStr = Json->GetStringField(TEXT("value"));
2026-03-10 07:17:42 -04:00
if (!ValueStr.IsEmpty()) E->Code = ValueStr;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
SetResult = FString::Printf(TEXT("code: %d chars"), E->Code.Len());
2026-03-08 22:17:14 -04:00
FString OutputTypeStr;
if (Json->TryGetStringField(TEXT("outputType"), OutputTypeStr) && !OutputTypeStr.IsEmpty())
{
2026-03-10 07:17:42 -04:00
ECustomMaterialOutputType OutType;
if (MCPUtils::StringToEnum(OutputTypeStr, OutType, MCPErrorCallback(Result)))
E->OutputType = OutType;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
return true;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
if (auto* E = Cast<UMaterialExpressionComponentMask>(Expr))
2026-03-08 22:17:14 -04:00
{
const TSharedPtr<FJsonObject>* ValueObj = nullptr;
2026-03-10 07:17:42 -04:00
if (!Json->TryGetObjectField(TEXT("value"), ValueObj) || !ValueObj || !(*ValueObj).IsValid())
2026-03-08 22:17:14 -04:00
{
2026-03-10 07:17:42 -04:00
Result.Append(TEXT("ERROR: ComponentMask requires value as {r, g, b, a} (booleans)\n"));
return false;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
bool bR = false, bG = false, bB = false, bA = false;
(*ValueObj)->TryGetBoolField(TEXT("r"), bR);
(*ValueObj)->TryGetBoolField(TEXT("g"), bG);
(*ValueObj)->TryGetBoolField(TEXT("b"), bB);
(*ValueObj)->TryGetBoolField(TEXT("a"), bA);
E->R = bR ? 1 : 0;
E->G = bG ? 1 : 0;
E->B = bB ? 1 : 0;
E->A = bA ? 1 : 0;
SetResult = FString::Printf(TEXT("R=%s G=%s B=%s A=%s"),
bR ? TEXT("true") : TEXT("false"),
bG ? TEXT("true") : TEXT("false"),
bB ? TEXT("true") : TEXT("false"),
bA ? TEXT("true") : TEXT("false"));
return true;
2026-03-08 22:17:14 -04:00
}
2026-03-10 07:17:42 -04:00
Result.Appendf(TEXT("ERROR: Expression type '%s' does not support value setting. Supported: "
"Constant, Constant3Vector, Constant4Vector, ScalarParameter, VectorParameter, "
"TextureCoordinate, Custom, ComponentMask\n"),
*Expr->GetClass()->GetName());
return false;
}
2026-03-08 22:17:14 -04:00
2026-03-10 07:17:42 -04:00
// Parse {r, g, b[, a]} from the "value" JSON field.
bool ParseColorValue(const FJsonObject* Json, FLinearColor& OutColor, bool bHasAlpha, FStringBuilderBase& Result)
{
const TSharedPtr<FJsonObject>* ValueObj = nullptr;
if (!Json->TryGetObjectField(TEXT("value"), ValueObj) || !ValueObj || !(*ValueObj).IsValid())
{
Result.Appendf(TEXT("ERROR: requires value as {r, g, b%s}\n"), bHasAlpha ? TEXT(", a") : TEXT(""));
return false;
}
double R = 0, G = 0, B = 0, A = 1;
(*ValueObj)->TryGetNumberField(TEXT("r"), R);
(*ValueObj)->TryGetNumberField(TEXT("g"), G);
(*ValueObj)->TryGetNumberField(TEXT("b"), B);
if (bHasAlpha) (*ValueObj)->TryGetNumberField(TEXT("a"), A);
OutColor = FLinearColor((float)R, (float)G, (float)B, (float)A);
return true;
}
2026-03-08 22:17:14 -04:00
2026-03-10 07:17:42 -04:00
// If the JSON has a "parameterName" field, apply it to a parameter expression.
void ApplyParameterName(UMaterialExpressionParameter* Param, const FJsonObject* Json)
{
FString ParamName;
if (Json->TryGetStringField(TEXT("parameterName"), ParamName) && !ParamName.IsEmpty())
Param->ParameterName = FName(*ParamName);
2026-03-08 22:17:14 -04:00
}
};