More work on handlers

This commit is contained in:
2026-03-12 17:20:20 -04:00
parent c7fb52a775
commit 8cf847280c
7 changed files with 109 additions and 120 deletions

View File

@@ -83,12 +83,12 @@ public:
FBlendParameter& MutableParamY = const_cast<FBlendParameter&>(ParamY);
if (!AxisXName.IsEmpty()) MutableParamX.DisplayName = AxisXName;
if (Json->HasField(TEXT("axisXMin"))) MutableParamX.Min = AxisXMin;
if (Json->HasField(TEXT("axisXMax"))) MutableParamX.Max = AxisXMax;
if (AxisXMin != 0.0f) MutableParamX.Min = AxisXMin;
if (AxisXMax != 0.0f) MutableParamX.Max = AxisXMax;
if (!AxisYName.IsEmpty()) MutableParamY.DisplayName = AxisYName;
if (Json->HasField(TEXT("axisYMin"))) MutableParamY.Min = AxisYMin;
if (Json->HasField(TEXT("axisYMax"))) MutableParamY.Max = AxisYMax;
if (AxisYMin != 0.0f) MutableParamY.Min = AxisYMin;
if (AxisYMax != 0.0f) MutableParamY.Max = AxisYMax;
// Clear existing samples (delete from end to start)
int32 NumExisting = BS->GetNumberOfBlendSamples();

View File

@@ -82,8 +82,6 @@ public:
}
// Apply optional properties.
bool bHasTwoSided = Json->HasField(TEXT("twoSided"));
TArray<UObject*> Chain = { MaterialObj };
MCPUtils::PreEdit(Chain);
@@ -93,8 +91,7 @@ public:
if (!BlendMode.IsEmpty())
MaterialObj->BlendMode = ParsedBlendMode;
if (bHasTwoSided)
MaterialObj->TwoSided = TwoSided;
MaterialObj->TwoSided = TwoSided ? 1 : 0;
MCPUtils::PostEdit(Chain);

View File

@@ -0,0 +1,48 @@
#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPFetcher.h"
#include "MCPUtils.h"
#include "Materials/Material.h"
#include "MaterialDomain.h"
#include "Material_DumpProperties.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCP_Material_DumpProperties : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Material path"))
FString Path;
virtual FString GetDescription() const override
{
return TEXT("List top-level material properties such as domain, blend mode, shading model, and usage flags.");
}
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
{
MCPFetcher F(Result);
UMaterial* Mat = F.Asset(Path).Cast<UMaterial>();
if (!Mat) return;
Result.Appendf(TEXT("Material: %s\n"), *MCPUtils::FormatName(Mat));
Result.Appendf(TEXT(" domain = %s\n"), *MCPUtils::EnumToString(Mat->MaterialDomain, TEXT("MD_")));
Result.Appendf(TEXT(" blendMode = %s\n"), *MCPUtils::EnumToString(Mat->BlendMode, TEXT("BLEND_")));
Result.Appendf(TEXT(" shadingModel = %s\n"), *MCPUtils::EnumToString(Mat->GetShadingModels().GetFirstShadingModel(), TEXT("MSM_")));
Result.Appendf(TEXT(" opacityMaskClipValue = %g\n"), Mat->OpacityMaskClipValue);
Result.Appendf(TEXT(" twoSided = %s\n"), Mat->TwoSided ? TEXT("true") : TEXT("false"));
Result.Appendf(TEXT(" bUsedWithSkeletalMesh = %s\n"), Mat->bUsedWithSkeletalMesh ? TEXT("true") : TEXT("false"));
Result.Appendf(TEXT(" bUsedWithMorphTargets = %s\n"), Mat->bUsedWithMorphTargets ? TEXT("true") : TEXT("false"));
Result.Appendf(TEXT(" bUsedWithNiagaraSprites = %s\n"), Mat->bUsedWithNiagaraSprites ? TEXT("true") : TEXT("false"));
Result.Appendf(TEXT(" ditheredLODTransition = %s\n"), Mat->DitheredLODTransition ? TEXT("true") : TEXT("false"));
Result.Appendf(TEXT(" bAllowNegativeEmissiveColor = %s\n"), Mat->bAllowNegativeEmissiveColor ? TEXT("true") : TEXT("false"));
}
};

View File

@@ -2,7 +2,7 @@
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPAssetFinder.h"
#include "MCPFetcher.h"
#include "MCPUtils.h"
#include "Materials/Material.h"
#include "MaterialDomain.h"
@@ -19,102 +19,81 @@ class UMCP_Material_SetProperty : public UObject, public IMCPHandler
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Material name or package path"))
FString Material;
UPROPERTY(meta=(Description="Material path"))
FString Path;
UPROPERTY(meta=(Description="Property name to set (domain, blendMode, twoSided, shadingModel, opacity, opacityMaskClipValue, bUsedWithSkeletalMesh, bUsedWithMorphTargets, bUsedWithNiagaraSprites, ditheredLODTransition, bAllowNegativeEmissiveColor)"))
FString Property;
UPROPERTY(meta=(Optional, Description="If true, preview the change without applying it"))
bool DryRun = false;
UPROPERTY(meta=(Description="Value to set"))
FString Value;
virtual FString GetDescription() const override
{
return TEXT("Set a top-level material property such as domain, blend mode, shading model, or usage flags. "
"The 'value' field in the JSON payload provides the new value.");
return TEXT("Set a top-level material property such as domain, blend mode, shading model, or usage flags.");
}
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
{
if (!Json->HasField(TEXT("value")))
{
Result.Append(TEXT("ERROR: Missing required field: value\n"));
return;
}
MCPFetcher F(Result);
UMaterial* Mat = F.Asset(Path).Cast<UMaterial>();
if (!Mat) return;
// Load material
MCPAssets<UMaterial> Assets;
if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return;
UMaterial* Mat = Assets.Object();
FString OldValue;
FString NewValue;
// Helper: apply a bool property change.
auto SetBool = [&](auto Getter, auto Setter) {
bool bValue = Json->GetBoolField(TEXT("value"));
OldValue = Getter() ? TEXT("true") : TEXT("false");
NewValue = bValue ? TEXT("true") : TEXT("false");
if (!DryRun) Setter(bValue);
};
// Parse value and build setter. Validation happens here so we can bail before PreEdit.
TFunction<void()> Setter;
if (Property == TEXT("domain"))
{
FString ValueStr = Json->GetStringField(TEXT("value"));
OldValue = MCPUtils::EnumToString(Mat->MaterialDomain, TEXT("MD_"));
EMaterialDomain NewDomain;
if (!MCPUtils::StringToEnum(ValueStr, NewDomain, Result, TEXT("MD_"))) return;
NewValue = MCPUtils::EnumToString(NewDomain, TEXT("MD_"));
if (!DryRun) Mat->MaterialDomain = NewDomain;
if (!MCPUtils::StringToEnum(Value, NewDomain, Result, TEXT("MD_"))) return;
Setter = [Mat, NewDomain]() { Mat->MaterialDomain = NewDomain; };
}
else if (Property == TEXT("blendMode"))
{
FString ValueStr = Json->GetStringField(TEXT("value"));
OldValue = MCPUtils::EnumToString(Mat->BlendMode, TEXT("BLEND_"));
EBlendMode NewBlend;
if (!MCPUtils::StringToEnum(ValueStr, NewBlend, Result, TEXT("BLEND_"))) return;
NewValue = MCPUtils::EnumToString(NewBlend, TEXT("BLEND_"));
if (!DryRun) Mat->BlendMode = NewBlend;
if (!MCPUtils::StringToEnum(Value, NewBlend, Result, TEXT("BLEND_"))) return;
Setter = [Mat, NewBlend]() { Mat->BlendMode = NewBlend; };
}
else if (Property == TEXT("shadingModel"))
{
FString ValueStr = Json->GetStringField(TEXT("value"));
OldValue = MCPUtils::EnumToString(Mat->GetShadingModels().GetFirstShadingModel(), TEXT("MSM_"));
EMaterialShadingModel NewModel;
if (!MCPUtils::StringToEnum(ValueStr, NewModel, Result, TEXT("MSM_"))) return;
NewValue = MCPUtils::EnumToString(NewModel, TEXT("MSM_"));
if (!DryRun) Mat->SetShadingModel(NewModel);
if (!MCPUtils::StringToEnum(Value, NewModel, Result, TEXT("MSM_"))) return;
Setter = [Mat, NewModel]() { Mat->SetShadingModel(NewModel); };
}
else if (Property == TEXT("opacity") || Property == TEXT("opacityMaskClipValue"))
{
double OpacityValue = Json->GetNumberField(TEXT("value"));
OldValue = FString::Printf(TEXT("%g"), Mat->OpacityMaskClipValue);
NewValue = FString::Printf(TEXT("%g"), OpacityValue);
if (!DryRun) Mat->OpacityMaskClipValue = (float)OpacityValue;
float FloatVal = FCString::Atof(*Value);
Setter = [Mat, FloatVal]() { Mat->OpacityMaskClipValue = FloatVal; };
}
else if (Property == TEXT("twoSided"))
{
SetBool([&]{ return Mat->TwoSided; }, [&](bool v){ Mat->TwoSided = v ? 1 : 0; });
bool bVal; if (!MCPUtils::StringToBool(Value, bVal, Result)) return;
Setter = [Mat, bVal]() { Mat->TwoSided = bVal ? 1 : 0; };
}
else if (Property == TEXT("bUsedWithSkeletalMesh"))
{
SetBool([&]{ return Mat->bUsedWithSkeletalMesh; }, [&](bool v){ Mat->bUsedWithSkeletalMesh = v ? 1 : 0; });
bool bVal; if (!MCPUtils::StringToBool(Value, bVal, Result)) return;
Setter = [Mat, bVal]() { Mat->bUsedWithSkeletalMesh = bVal ? 1 : 0; };
}
else if (Property == TEXT("bUsedWithMorphTargets"))
{
SetBool([&]{ return Mat->bUsedWithMorphTargets; }, [&](bool v){ Mat->bUsedWithMorphTargets = v ? 1 : 0; });
bool bVal; if (!MCPUtils::StringToBool(Value, bVal, Result)) return;
Setter = [Mat, bVal]() { Mat->bUsedWithMorphTargets = bVal ? 1 : 0; };
}
else if (Property == TEXT("bUsedWithNiagaraSprites"))
{
SetBool([&]{ return Mat->bUsedWithNiagaraSprites; }, [&](bool v){ Mat->bUsedWithNiagaraSprites = v ? 1 : 0; });
bool bVal; if (!MCPUtils::StringToBool(Value, bVal, Result)) return;
Setter = [Mat, bVal]() { Mat->bUsedWithNiagaraSprites = bVal ? 1 : 0; };
}
else if (Property == TEXT("ditheredLODTransition") || Property == TEXT("DitheredLODTransition"))
{
SetBool([&]{ return Mat->DitheredLODTransition; }, [&](bool v){ Mat->DitheredLODTransition = v ? 1 : 0; });
bool bVal; if (!MCPUtils::StringToBool(Value, bVal, Result)) return;
Setter = [Mat, bVal]() { Mat->DitheredLODTransition = bVal ? 1 : 0; };
}
else if (Property == TEXT("bAllowNegativeEmissiveColor"))
{
SetBool([&]{ return Mat->bAllowNegativeEmissiveColor; }, [&](bool v){ Mat->bAllowNegativeEmissiveColor = v ? 1 : 0; });
bool bVal; if (!MCPUtils::StringToBool(Value, bVal, Result)) return;
Setter = [Mat, bVal]() { Mat->bAllowNegativeEmissiveColor = bVal ? 1 : 0; };
}
else
{
@@ -124,18 +103,12 @@ public:
return;
}
// Notify and save if not dry run
if (!DryRun)
{
TArray<UObject*> Chain = { Mat };
MCPUtils::PreEdit(Chain);
MCPUtils::PostEdit(Chain);
if (!MCPUtils::SaveMaterialPackage(Mat))
Result.Append(TEXT("WARNING: Package save failed\n"));
}
// Apply the change between PreEdit/PostEdit.
F.PreEdit();
Setter();
F.PostEdit();
MCPUtils::SaveMaterialPackage(Mat);
Result.Appendf(TEXT("%s%s: %s -> %s\n"),
DryRun ? TEXT("[DRY RUN] ") : TEXT(""),
*Property, *OldValue, *NewValue);
Result.Appendf(TEXT("Set %s on %s\n"), *Property, *MCPUtils::FormatName(Mat));
}
};

View File

@@ -84,18 +84,9 @@ public:
TransNode->CreateConnections(FromStateNode, ToStateNode);
// Set optional properties
if (Json->HasField(TEXT("crossfadeDuration")))
{
TransNode->CrossfadeDuration = CrossfadeDuration;
}
if (Json->HasField(TEXT("priority")))
{
TransNode->PriorityOrder = Priority;
}
if (Json->HasField(TEXT("bBidirectional")))
{
TransNode->Bidirectional = BBidirectional;
}
TransNode->CrossfadeDuration = CrossfadeDuration;
TransNode->PriorityOrder = Priority;
TransNode->Bidirectional = BBidirectional;
// Compile and save
FKismetEditorUtilities::CompileBlueprint(AnimBP);

View File

@@ -74,40 +74,12 @@ public:
return;
}
// Count and apply property changes
int32 ChangedCount = 0;
if (Json->HasField(TEXT("crossfadeDuration")))
{
TransNode->CrossfadeDuration = CrossfadeDuration;
ChangedCount++;
}
if (Json->HasField(TEXT("blendMode")))
{
TransNode->BlendMode = (EAlphaBlendOption)BlendMode;
ChangedCount++;
}
if (Json->HasField(TEXT("priorityOrder")))
{
TransNode->PriorityOrder = PriorityOrder;
ChangedCount++;
}
if (Json->HasField(TEXT("logicType")))
{
TransNode->LogicType = (ETransitionLogicType::Type)LogicType;
ChangedCount++;
}
if (Json->HasField(TEXT("bBidirectional")))
{
TransNode->Bidirectional = BBidirectional;
ChangedCount++;
}
if (ChangedCount == 0)
{
Result.Append(TEXT("ERROR: No properties to update. Provide at least one of: crossfadeDuration, blendMode, priorityOrder, logicType, bBidirectional\n"));
return;
}
// Apply properties
TransNode->CrossfadeDuration = CrossfadeDuration;
TransNode->BlendMode = (EAlphaBlendOption)BlendMode;
TransNode->PriorityOrder = PriorityOrder;
TransNode->LogicType = (ETransitionLogicType::Type)LogicType;
TransNode->Bidirectional = BBidirectional;
TArray<UObject*> Chain = { TransNode };
MCPUtils::PreEdit(Chain);
@@ -117,7 +89,7 @@ public:
FKismetEditorUtilities::CompileBlueprint(AnimBP);
MCPUtils::SaveBlueprintPackage(AnimBP);
Result.Appendf(TEXT("Updated %d properties on transition %s -> %s: %s\n"),
ChangedCount, *FromState, *ToState, *MCPUtils::FormatName(TransNode));
Result.Appendf(TEXT("Updated transition %s -> %s: %s\n"),
*FromState, *ToState, *MCPUtils::FormatName(TransNode));
}
};

View File

@@ -218,6 +218,14 @@ public:
return true;
}
static bool StringToBool(const FString& Str, bool& OutValue, MCPErrorCallback Error)
{
if (Str.Equals(TEXT("true"), ESearchCase::IgnoreCase)) { OutValue = true; return true; }
if (Str.Equals(TEXT("false"), ESearchCase::IgnoreCase)) { OutValue = false; return true; }
Error.SetError(FString::Printf(TEXT("Invalid bool value '%s' (expected 'true' or 'false')"), *Str));
return false;
}
// ----- Blueprint helpers -----
static TArray<UEdGraph*> AllGraphs(UBlueprint* BP);
static TArray<UEdGraph*> AllGraphsNamed(UBlueprint* BP, const FString& Name);