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/MaterialExpression.h"
|
2026-03-10 07:17:42 -04:00
|
|
|
#include "Materials/MaterialFunction.h"
|
2026-03-08 22:17:14 -04:00
|
|
|
#include "MaterialGraph/MaterialGraph.h"
|
|
|
|
|
#include "MaterialGraph/MaterialGraphNode.h"
|
|
|
|
|
#include "UMCPHandler_SetMaterialExpressionPosition.generated.h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
// ---------------------------------------------------------------------------
|
|
|
|
|
|
2026-03-11 22:03:32 -04:00
|
|
|
UCLASS(meta=(Group="Unclassified"))
|
2026-03-08 22:17:14 -04:00
|
|
|
class UMCPHandler_SetMaterialExpressionPosition : 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 name (use FormatName from DumpMaterial output)"))
|
2026-03-08 22:17:14 -04:00
|
|
|
FString Node;
|
|
|
|
|
|
|
|
|
|
UPROPERTY(meta=(Description="New X position"))
|
|
|
|
|
int32 PosX = 0;
|
|
|
|
|
|
|
|
|
|
UPROPERTY(meta=(Description="New Y position"))
|
|
|
|
|
int32 PosY = 0;
|
|
|
|
|
|
|
|
|
|
UPROPERTY(meta=(Optional, Description="If true, preview the change without applying it"))
|
|
|
|
|
bool DryRun = false;
|
|
|
|
|
|
|
|
|
|
virtual FString GetDescription() const override
|
|
|
|
|
{
|
|
|
|
|
return TEXT("Reposition a material expression node in the material graph editor.");
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
MCPErrorCallback(Result).SetError(TEXT("Specify 'material' or 'materialFunction'."));
|
|
|
|
|
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
|
|
|
MCPErrorCallback(Result).SetError(TEXT("Asset has no material graph."));
|
|
|
|
|
return;
|
2026-03-08 22:17:14 -04:00
|
|
|
}
|
|
|
|
|
|
2026-03-10 07:17:42 -04:00
|
|
|
// Find node by name
|
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
|
|
|
UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(GraphNode);
|
|
|
|
|
if (!MatNode || !MatNode->MaterialExpression) continue;
|
|
|
|
|
if (MCPUtils::Identifies(Node, MatNode->MaterialExpression))
|
2026-03-08 22:17:14 -04:00
|
|
|
{
|
2026-03-10 07:17:42 -04:00
|
|
|
TargetMatNode = MatNode;
|
2026-03-08 22:17:14 -04:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!TargetMatNode)
|
|
|
|
|
{
|
2026-03-10 07:17:42 -04:00
|
|
|
MCPErrorCallback(Result).SetError(FString::Printf(TEXT("Expression '%s' not found."), *Node));
|
|
|
|
|
return;
|
2026-03-08 22:17:14 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (DryRun)
|
|
|
|
|
{
|
2026-03-10 07:17:42 -04:00
|
|
|
Result.Appendf(TEXT("DryRun: would move %s to (%d, %d)\n"),
|
|
|
|
|
*MCPUtils::FormatName(TargetMatNode->MaterialExpression), PosX, PosY);
|
2026-03-08 22:17:14 -04:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set position on the graph node
|
|
|
|
|
TargetMatNode->NodePosX = PosX;
|
|
|
|
|
TargetMatNode->NodePosY = PosY;
|
|
|
|
|
|
|
|
|
|
// Also update the underlying expression position
|
|
|
|
|
if (TargetMatNode->MaterialExpression)
|
|
|
|
|
{
|
|
|
|
|
TargetMatNode->MaterialExpression->MaterialExpressionEditorX = PosX;
|
|
|
|
|
TargetMatNode->MaterialExpression->MaterialExpressionEditorY = PosY;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UObject* Asset = MaterialObj ? (UObject*)MaterialObj : (UObject*)MatFunc;
|
|
|
|
|
Asset->PreEditChange(nullptr);
|
|
|
|
|
Asset->PostEditChange();
|
|
|
|
|
|
|
|
|
|
// Save
|
|
|
|
|
bool bSaved = MaterialObj ? MCPUtils::SaveMaterialPackage(MaterialObj) : MCPUtils::SaveGenericPackage(MatFunc);
|
|
|
|
|
|
2026-03-10 07:17:42 -04:00
|
|
|
Result.Appendf(TEXT("Moved %s to (%d, %d)"),
|
|
|
|
|
*MCPUtils::FormatName(TargetMatNode->MaterialExpression), PosX, PosY);
|
|
|
|
|
if (!bSaved) Result.Append(TEXT(" (save failed)"));
|
|
|
|
|
Result.Append(TEXT("\n"));
|
2026-03-08 22:17:14 -04:00
|
|
|
}
|
|
|
|
|
};
|