Files
integration/Plugins/BlueprintMCP/Deprecated/UMCPHandler_AddMaterialExpression.h
2026-03-13 14:26:04 -04:00

170 lines
5.2 KiB
C++

#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPAssets.h"
#include "MCPUtils.h"
#include "Materials/Material.h"
#include "Materials/MaterialFunction.h"
#include "Materials/MaterialExpression.h"
#include "MaterialGraph/MaterialGraph.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "UObject/UObjectIterator.h"
#include "UMCPHandler_AddMaterialExpression.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS(meta=(Group="Unclassified"))
class UMCPHandler_AddMaterialExpression : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Material name or package path (specify this or materialFunction, not both)"))
FString Material;
UPROPERTY(meta=(Optional, Description="Material function name or package path (specify this or material, not both)"))
FString MaterialFunction;
UPROPERTY(meta=(Description="Expression class name without 'MaterialExpression' prefix (e.g. 'Constant', 'ScalarParameter', 'Add', 'Multiply', 'Lerp')"))
FString ExpressionClass;
UPROPERTY(meta=(Optional, Description="X position in the material graph editor"))
int32 PosX = 0;
UPROPERTY(meta=(Optional, Description="Y position in the material graph editor"))
int32 PosY = 0;
virtual FString GetDescription() const override
{
return TEXT("Add a new expression node to a material or material function graph.");
}
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
{
if (Material.IsEmpty() && MaterialFunction.IsEmpty())
{
Result.Append(TEXT("ERROR: Specify 'material' or 'materialFunction'\n"));
return;
}
if (!Material.IsEmpty() && !MaterialFunction.IsEmpty())
{
Result.Append(TEXT("ERROR: Specify 'material' or 'materialFunction', not both\n"));
return;
}
// Resolve the expression class
UClass* ExprClass = ResolveExpressionClass(Result);
if (!ExprClass) return;
// Load material or material function
UMaterial* MaterialObj = nullptr;
UMaterialFunction* MatFunc = nullptr;
UObject* Owner = nullptr;
if (!MaterialFunction.IsEmpty())
{
MCPAssets<UMaterialFunction> Assets;
if (!Assets.Exact(MaterialFunction).Errors(Result).ENone().ETwo().Load()) return;
MatFunc = Assets.Object();
Owner = MatFunc;
}
else
{
MCPAssets<UMaterial> Assets;
if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
MaterialObj = Assets.Object();
Owner = MaterialObj;
}
// Ensure the MaterialGraph exists (commandlet mode doesn't auto-create it)
if (MaterialObj) MCPUtils::EnsureMaterialGraph(MaterialObj);
// Create the expression
UMaterialExpression* NewExpr = NewObject<UMaterialExpression>(Owner, ExprClass);
if (!NewExpr)
{
Result.Append(TEXT("ERROR: Failed to create material expression object\n"));
return;
}
NewExpr->MaterialExpressionEditorX = PosX;
NewExpr->MaterialExpressionEditorY = PosY;
if (MaterialObj)
{
MaterialObj->GetExpressionCollection().AddExpression(NewExpr);
if (MaterialObj->MaterialGraph)
MaterialObj->MaterialGraph->RebuildGraph();
MaterialObj->PreEditChange(nullptr);
MaterialObj->PostEditChange();
MaterialObj->MarkPackageDirty();
}
else
{
MatFunc->GetExpressionCollection().AddExpression(NewExpr);
MatFunc->PreEditChange(nullptr);
MatFunc->PostEditChange();
MatFunc->MarkPackageDirty();
}
// Save
bool bSaved = MaterialObj
? MCPUtils::SaveMaterialPackage(MaterialObj)
: MCPUtils::SaveGenericPackage(MatFunc);
// Output
Result.Appendf(TEXT("Added %s\n"), *MCPUtils::FormatName(NewExpr));
// Find the graph node GUID (only for materials with a material graph)
if (MaterialObj && MaterialObj->MaterialGraph)
{
for (UEdGraphNode* Node : MaterialObj->MaterialGraph->Nodes)
{
UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node);
if (MatNode && MatNode->MaterialExpression == NewExpr)
{
Result.Appendf(TEXT("NodeId: %s\n"), *Node->NodeGuid.ToString());
break;
}
}
}
if (!bSaved)
Result.Append(TEXT("WARNING: Failed to save package\n"));
}
private:
UClass* ResolveExpressionClass(FStringBuilderBase& Result)
{
// Convenience aliases
static TMap<FString, FString> Aliases = {
{TEXT("Lerp"), TEXT("LinearInterpolate")},
};
FString LookupName = ExpressionClass;
if (const FString* Alias = Aliases.Find(ExpressionClass))
LookupName = *Alias;
FString FullClassName = FString::Printf(TEXT("MaterialExpression%s"), *LookupName);
for (TObjectIterator<UClass> It; It; ++It)
{
if (It->GetName() == FullClassName && It->IsChildOf(UMaterialExpression::StaticClass()))
{
if (It->HasAnyClassFlags(CLASS_Abstract))
{
Result.Appendf(TEXT("ERROR: Expression class '%s' is abstract\n"), *ExpressionClass);
return nullptr;
}
return *It;
}
}
Result.Appendf(TEXT("ERROR: Unknown expression class '%s'. Use the UMaterialExpression subclass name without the 'MaterialExpression' prefix (e.g. 'Constant', 'ScalarParameter', 'Add', 'Multiply', 'Lerp')\n"),
*ExpressionClass);
return nullptr;
}
};