diff --git a/Content/Testing/M_Test.uasset b/Content/Testing/M_Test.uasset index 47cba279..67b665f4 100644 --- a/Content/Testing/M_Test.uasset +++ b/Content/Testing/M_Test.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:296d949b2dadcc103f32ad1549ea8405259d9744d0023d8de10790d0335f3ffd -size 14297 +oid sha256:4ff518f26fa64b769892a751f3018a1cc7f8f69a99ff5893b09d876ac9dcdd82 +size 14276 diff --git a/Content/Testing/M_Test_Inst.uasset b/Content/Testing/M_Test_Inst.uasset index 89918215..e0366443 100644 --- a/Content/Testing/M_Test_Inst.uasset +++ b/Content/Testing/M_Test_Inst.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8025bae950d02457c5095c48aa30cdcdf0b628d6a9f5019120e0f721060c8d03 -size 9315 +oid sha256:707150b1787ffacc956c89e36ed76efc536125b266cf8fca35a6bfc447875e08 +size 8986 diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_ClearParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_ClearParameter.h new file mode 100644 index 00000000..3d0d93dc --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_ClearParameter.h @@ -0,0 +1,80 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPHandler.h" +#include "MCPFetcher.h" +#include "MCPUtils.h" +#include "Materials/MaterialInstanceConstant.h" +#include "MaterialTypes.h" +#include "MaterialInstance_ClearParameter.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UMCP_MaterialInstance_ClearParameter : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Material Instance path")) + FString Path; + + UPROPERTY(meta=(Description="Parameter name to clear")) + FString Parameter; + + UPROPERTY(meta=(Description="Parameter association: 'Global', 'Layer', or 'Blend'. Default: 'Global'", Optional)) + FString ParameterAssociation = TEXT("Global"); + + UPROPERTY(meta=(Description="Layer/blend index (0-based). Only used when ParameterAssociation is 'Layer' or 'Blend'", Optional)) + int32 ParameterLayer = INDEX_NONE; + + virtual FString GetDescription() const override + { + return TEXT("Remove a parameter override from a Material Instance, reverting it to the parent material's value."); + } + + virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override + { + MCPFetcher F(Result); + UMaterialInstanceConstant* MI = F.Asset(Path).Cast(); + if (!MI) return; + + // Parse the association string. + EMaterialParameterAssociation Association; + if (!MCPUtils::ParseMaterialParameterAssociation(ParameterAssociation, Association, Result)) + return; + + FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer); + + // Remove the override from all parameter arrays. + auto RemoveFrom = [&](auto& Arr) { + return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ParamID; }); + }; + + F.PreEdit(); + int32 Removed = 0; + Removed += RemoveFrom(MI->ScalarParameterValues); + Removed += RemoveFrom(MI->VectorParameterValues); + Removed += RemoveFrom(MI->DoubleVectorParameterValues); + Removed += RemoveFrom(MI->TextureParameterValues); + Removed += RemoveFrom(MI->TextureCollectionParameterValues); + Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues); + Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues); + Removed += RemoveFrom(MI->FontParameterValues); + F.PostEdit(); + + if (Removed == 0) + { + Result.Appendf(TEXT("No override found for parameter '%s' (association=%s layer=%d) on %s"), + *Parameter, *ParameterAssociation, ParameterLayer, *MCPUtils::FormatName(MI)); + return; + } + + MCPUtils::SaveGenericPackage(MI); + Result.Appendf(TEXT("Cleared override for '%s' on %s\n"), + *Parameter, *MCPUtils::FormatName(MI)); + } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_DumpParameters.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_DumpParameters.h index 4d36421f..76a01638 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_DumpParameters.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_DumpParameters.h @@ -2,16 +2,10 @@ #include "CoreMinimal.h" #include "MCPHandler.h" -#include "MCPAssetFinder.h" +#include "MCPFetcher.h" #include "MCPUtils.h" -#include "Materials/Material.h" -#include "Materials/MaterialInterface.h" #include "Materials/MaterialInstanceConstant.h" -#include "Materials/MaterialExpressionScalarParameter.h" -#include "Materials/MaterialExpressionVectorParameter.h" -#include "Materials/MaterialExpressionTextureSampleParameter2D.h" -#include "Materials/MaterialExpressionStaticSwitchParameter.h" -#include "Engine/Texture.h" +#include "MaterialTypes.h" #include "MaterialInstance_DumpParameters.generated.h" @@ -25,144 +19,38 @@ class UMCP_MaterialInstance_DumpParameters : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Material Instance name or path to inspect")) - FString MaterialInstance; + UPROPERTY(meta=(Description="Material Instance path")) + FString Path; virtual FString GetDescription() const override { - return TEXT("List all parameters on a Material Instance, including overridden and inherited parameters."); + return TEXT("List all parameters on a Material Instance, showing current values and which are overridden."); } virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override { - MCPAssets Assets; - if (!Assets.Exact(MaterialInstance).Errors(Result).ENone().ETwo().Load()) return; - UMaterialInstanceConstant* MI = Assets.Object(); + MCPFetcher F(Result); + UMaterialInstanceConstant* MI = F.Asset(Path).Cast(); + if (!MI) return; - Result.Appendf(TEXT("MaterialInstance: %s\n"), *MCPUtils::FormatName(MI)); + auto AllParams = MCPUtils::GetMaterialParameters(MI); - // Parent chain - if (MI->Parent) - { - Result.Append(TEXT("Parent chain:")); - UMaterialInterface* Current = MI->Parent; - while (Current) - { - if (UMaterial* Mat = Cast(Current)) - { - Result.Appendf(TEXT(" %s"), *MCPUtils::FormatName(Mat)); - break; - } - if (UMaterialInstance* ParentMI = Cast(Current)) - { - Result.Appendf(TEXT(" %s ->"), *MCPUtils::FormatName(ParentMI)); - UMaterialInstanceConstant* ParentMIC = Cast(Current); - Current = ParentMIC ? ParentMIC->Parent : nullptr; - } - else - { - Result.Appendf(TEXT(" %s"), *Current->GetPathName()); - break; - } - } - Result.Append(TEXT("\n")); - } - - // Collect names of overridden parameters for filtering inherited ones - TSet OverriddenScalars, OverriddenVectors, OverriddenTextures, OverriddenStaticSwitches; - for (const FScalarParameterValue& P : MI->ScalarParameterValues) - OverriddenScalars.Add(P.ParameterInfo.Name.ToString()); - for (const FVectorParameterValue& P : MI->VectorParameterValues) - OverriddenVectors.Add(P.ParameterInfo.Name.ToString()); - for (const FTextureParameterValue& P : MI->TextureParameterValues) - OverriddenTextures.Add(P.ParameterInfo.Name.ToString()); - { - FStaticParameterSet SP; - MI->GetStaticParameterValues(SP); - for (const FStaticSwitchParameter& P : SP.StaticSwitchParameters) - if (P.bOverride) - OverriddenStaticSwitches.Add(P.ParameterInfo.Name.ToString()); - } - - // Overridden parameters + // Overridden parameters first. bool bHasOverrides = false; - auto EnsureOverrideHeader = [&]() { + for (auto& [Info, Meta] : AllParams) + { + if (!Meta.bOverride) continue; if (!bHasOverrides) { Result.Append(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; } - }; - - for (const FScalarParameterValue& P : MI->ScalarParameterValues) - { - EnsureOverrideHeader(); - Result.Appendf(TEXT(" Scalar \"%s\" = %g\n"), *P.ParameterInfo.Name.ToString(), P.ParameterValue); - } - for (const FVectorParameterValue& P : MI->VectorParameterValues) - { - EnsureOverrideHeader(); - Result.Appendf(TEXT(" Vector \"%s\" = (%.3f, %.3f, %.3f, %.3f)\n"), - *P.ParameterInfo.Name.ToString(), - P.ParameterValue.R, P.ParameterValue.G, P.ParameterValue.B, P.ParameterValue.A); - } - for (const FTextureParameterValue& P : MI->TextureParameterValues) - { - EnsureOverrideHeader(); - Result.Appendf(TEXT(" Texture \"%s\" = %s\n"), - *P.ParameterInfo.Name.ToString(), - P.ParameterValue ? *MCPUtils::FormatName(P.ParameterValue) : TEXT("None")); - } - { - FStaticParameterSet StaticParams; - MI->GetStaticParameterValues(StaticParams); - for (const FStaticSwitchParameter& P : StaticParams.StaticSwitchParameters) - { - if (!P.bOverride) continue; - EnsureOverrideHeader(); - Result.Appendf(TEXT(" StaticSwitch \"%s\" = %s\n"), - *P.ParameterInfo.Name.ToString(), P.Value ? TEXT("true") : TEXT("false")); - } + MCPUtils::FormatMaterialParameter(Result, Info, Meta); } - // Inherited (non-overridden) parameters from the base material - UMaterial* BaseMat = MI->GetMaterial(); - if (!BaseMat) return; - + // Inherited (non-overridden) parameters. bool bHasInherited = false; - auto EnsureInheritedHeader = [&]() { - if (!bHasInherited) { Result.Append(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; } - }; - - for (UMaterialExpression* Expr : BaseMat->GetExpressions()) + for (auto& [Info, Meta] : AllParams) { - if (!Expr) continue; - - if (auto* SP = Cast(Expr)) - { - if (OverriddenScalars.Contains(SP->ParameterName.ToString())) continue; - EnsureInheritedHeader(); - Result.Appendf(TEXT(" Scalar \"%s\" default=%g\n"), *SP->ParameterName.ToString(), SP->DefaultValue); - } - else if (auto* VP = Cast(Expr)) - { - if (OverriddenVectors.Contains(VP->ParameterName.ToString())) continue; - EnsureInheritedHeader(); - Result.Appendf(TEXT(" Vector \"%s\" default=(%.3f, %.3f, %.3f, %.3f)\n"), - *VP->ParameterName.ToString(), - VP->DefaultValue.R, VP->DefaultValue.G, VP->DefaultValue.B, VP->DefaultValue.A); - } - else if (auto* TP = Cast(Expr)) - { - if (OverriddenTextures.Contains(TP->ParameterName.ToString())) continue; - EnsureInheritedHeader(); - Result.Appendf(TEXT(" Texture \"%s\" default=%s\n"), - *TP->ParameterName.ToString(), - TP->Texture ? *MCPUtils::FormatName(TP->Texture) : TEXT("None")); - } - else if (auto* SSP = Cast(Expr)) - { - if (OverriddenStaticSwitches.Contains(SSP->ParameterName.ToString())) continue; - EnsureInheritedHeader(); - Result.Appendf(TEXT(" StaticSwitch \"%s\" default=%s\n"), - *SSP->ParameterName.ToString(), SSP->DefaultValue ? TEXT("true") : TEXT("false")); - } + if (Meta.bOverride) continue; + if (!bHasInherited) { Result.Append(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; } + MCPUtils::FormatMaterialParameter(Result, Info, Meta); } } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_SetParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_SetParameter.h index fc6b8f88..19d097e4 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_SetParameter.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_SetParameter.h @@ -5,6 +5,7 @@ #include "MCPFetcher.h" #include "MCPUtils.h" #include "Materials/MaterialInstanceConstant.h" +#include "MaterialTypes.h" #include "Misc/DefaultValueHelper.h" #include "MaterialInstance_SetParameter.generated.h" @@ -25,6 +26,12 @@ public: UPROPERTY(meta=(Description="Parameter name to set")) FString Parameter; + UPROPERTY(meta=(Description="Parameter association: 'Global', 'Layer', or 'Blend'. Default: 'Global'", Optional)) + FString ParameterAssociation = TEXT("Global"); + + UPROPERTY(meta=(Description="Layer/blend index (0-based). Only used when ParameterAssociation is 'Layer' or 'Blend'", Optional)) + int32 ParameterLayer = INDEX_NONE; + UPROPERTY(meta=(Description="Value to set (uses Unreal text format, e.g. '0.5' for scalar, '(R=1,G=0,B=0,A=1)' for vector)")) FString Value; @@ -39,38 +46,30 @@ public: UMaterialInstanceConstant* MI = F.Asset(Path).Cast(); if (!MI) return; - auto Parameters = MCPUtils::MaterialParameters(MI); + // Parse the association string. + EMaterialParameterAssociation Association; + if (!MCPUtils::ParseMaterialParameterAssociation(ParameterAssociation, Association, Result)) + return; - // Find a parameter with a matching name. - FName Name(*Parameter); - MCPMaterialParameter* Found = nullptr; - int MatchCount = 0; - for (auto& P : Parameters) + // Build the parameter ID to look up. + FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer); + + // Find it in the material's parameter map. + auto AllParams = MCPUtils::GetMaterialParameters(MI); + FMaterialParameterMetadata* Found = AllParams.Find(ParamID); + if (!Found) { - if (P.Name == Name) - { - MatchCount++; - Found = &P; - } - } - if (MatchCount > 1) - { - Result.Appendf(TEXT("More than one parameter named '%s'"), *Parameter); + Result.Appendf(TEXT("No parameter named '%s' with association=%s layer=%d"), + *Parameter, *ParameterAssociation, ParameterLayer); return; } - if (MatchCount == 0) - { - Result.Appendf(TEXT("No parameter named '%s'"), *Parameter); - return; - } - if (Found->Metadata.PrimitiveDataIndex != INDEX_NONE) + if (Found->PrimitiveDataIndex != INDEX_NONE) { Result.Appendf(TEXT("Parameter '%s' uses custom primitive data and cannot be set on a material instance"), *Parameter); return; } - FMaterialParameterInfo Info(*Parameter); - EMaterialParameterType Type = Found->Metadata.Value.Type; + EMaterialParameterType Type = Found->Value.Type; F.PreEdit(); switch (Type) @@ -83,7 +82,7 @@ public: Result.Appendf(TEXT("Failed to parse '%s' as a float"), *Value); return; } - MI->SetScalarParameterValueEditorOnly(Info, ScalarValue); + MI->SetScalarParameterValueEditorOnly(ParamID, ScalarValue); break; } case EMaterialParameterType::Vector: @@ -94,11 +93,11 @@ public: Result.Appendf(TEXT("Failed to parse '%s' as a color/vector (expected format: '(R=1,G=0,B=0,A=1)')"), *Value); return; } - MI->SetVectorParameterValueEditorOnly(Info, Color); + MI->SetVectorParameterValueEditorOnly(ParamID, Color); break; } default: - Result.Appendf(TEXT("Unsupported parameter type for '%s'"), *Parameter); + Result.Appendf(TEXT("Parameters of type %d (see EMaterialParameterType) are not implemented"), (int)Type); return; } F.PostEdit(); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_DumpParameters.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_DumpParameters.h new file mode 100644 index 00000000..bf975c46 --- /dev/null +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_DumpParameters.h @@ -0,0 +1,43 @@ +#pragma once + +#include "CoreMinimal.h" +#include "MCPHandler.h" +#include "MCPFetcher.h" +#include "MCPUtils.h" +#include "Materials/Material.h" +#include "MaterialTypes.h" +#include "Material_DumpParameters.generated.h" + + +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- +// --------------------------------------------------------------------------- + +UCLASS() +class UMCP_Material_DumpParameters : public UObject, public IMCPHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Material path")) + FString Path; + + virtual FString GetDescription() const override + { + return TEXT("List all parameters on a Material, showing their default values."); + } + + virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override + { + MCPFetcher F(Result); + UMaterial* Mat = F.Asset(Path).Cast(); + if (!Mat) return; + + auto AllParams = MCPUtils::GetMaterialParameters(Mat); + + for (auto& [Info, Meta] : AllParams) + { + MCPUtils::FormatMaterialParameter(Result, Info, Meta); + } + } +}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetInstanceParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetInstanceParameter.h deleted file mode 100644 index 99481852..00000000 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetInstanceParameter.h +++ /dev/null @@ -1,206 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "MCPHandler.h" -#include "MCPAssetFinder.h" -#include "MCPUtils.h" -#include "Materials/Material.h" -#include "Materials/MaterialInterface.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Materials/MaterialExpressionScalarParameter.h" -#include "Materials/MaterialExpressionVectorParameter.h" -#include "Materials/MaterialExpressionTextureSampleParameter2D.h" -#include "Materials/MaterialExpressionStaticSwitchParameter.h" -#include "Engine/Texture.h" -#include "Material_SetInstanceParameter.generated.h" - - -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- - -UCLASS() -class UMCP_Material_SetInstanceParameter : public UObject, public IMCPHandler -{ - GENERATED_BODY() - -public: - UPROPERTY(meta=(Description="Material Instance name or path")) - FString MaterialInstance; - - UPROPERTY(meta=(Description="Parameter name to set")) - FString ParameterName; - - UPROPERTY(meta=(Description="Value to set (number for scalar, object with r/g/b/a for vector, string path for texture, bool for staticSwitch)")) - FMCPJsonObject Value; - - UPROPERTY(meta=(Optional, Description="Parameter type: scalar, vector, texture, staticSwitch. Auto-detected from parent if omitted.")) - FString Type; - - UPROPERTY(meta=(Optional, Description="If true, validate without applying changes")) - bool DryRun = false; - - virtual FString GetDescription() const override - { - return TEXT("Set a parameter override on a Material Instance. " - "Supports scalar, vector, texture, and static switch parameter types."); - } - - virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override - { - if (!Json->HasField(TEXT("value"))) - { - Result.Append(TEXT("ERROR: Missing required field: value\n")); - return; - } - - // Load the Material Instance - MCPAssets Assets; - if (!Assets.Exact(MaterialInstance).Errors(Result).ENone().ETwo().Load()) return; - UMaterialInstanceConstant* MI = Assets.Object(); - - // Determine the parameter type -- explicit or auto-detect from parent - FString TypeStr = Type; - if (TypeStr.IsEmpty()) - TypeStr = AutoDetectType(MI); - - if (TypeStr.IsEmpty()) - { - Result.Appendf(TEXT("ERROR: Could not determine parameter type for '%s'. Specify the 'type' field explicitly (scalar, vector, texture, staticSwitch).\n"), - *ParameterName); - return; - } - - FMaterialParameterInfo ParamInfo(*ParameterName); - FString NewValueDescription; - - if (TypeStr.Equals(TEXT("scalar"), ESearchCase::IgnoreCase)) - { - double FloatValue = Json->GetNumberField(TEXT("value")); - if (!DryRun) - MI->SetScalarParameterValueEditorOnly(ParamInfo, (float)FloatValue); - NewValueDescription = FString::Printf(TEXT("%g"), FloatValue); - } - else if (TypeStr.Equals(TEXT("vector"), ESearchCase::IgnoreCase)) - { - const TSharedPtr* ValueObj = nullptr; - if (!Json->TryGetObjectField(TEXT("value"), ValueObj) || !ValueObj || !(*ValueObj).IsValid()) - { - Result.Append(TEXT("ERROR: For vector parameters, 'value' must be an object with r, g, b (and optional a) fields.\n")); - return; - } - - double R = (*ValueObj)->GetNumberField(TEXT("r")); - double G = (*ValueObj)->GetNumberField(TEXT("g")); - double B = (*ValueObj)->GetNumberField(TEXT("b")); - double A = (*ValueObj)->HasField(TEXT("a")) ? (*ValueObj)->GetNumberField(TEXT("a")) : 1.0; - FLinearColor Color((float)R, (float)G, (float)B, (float)A); - - if (!DryRun) - MI->SetVectorParameterValueEditorOnly(ParamInfo, Color); - NewValueDescription = FString::Printf(TEXT("(%.3f, %.3f, %.3f, %.3f)"), R, G, B, A); - } - else if (TypeStr.Equals(TEXT("texture"), ESearchCase::IgnoreCase)) - { - FString TexturePath = Json->GetStringField(TEXT("value")); - if (TexturePath.IsEmpty()) - { - Result.Append(TEXT("ERROR: For texture parameters, 'value' must be a texture asset path string.\n")); - return; - } - - MCPAssets TexAssets; - if (!TexAssets.Exact(TexturePath).AllContent().Errors(Result).ENone().ETwo().Load()) return; - UTexture* TextureObj = TexAssets.Object(); - - if (!DryRun) - MI->SetTextureParameterValueEditorOnly(ParamInfo, TextureObj); - NewValueDescription = MCPUtils::FormatName(TextureObj); - } - else if (TypeStr.Equals(TEXT("staticSwitch"), ESearchCase::IgnoreCase)) - { - bool bSwitchValue = Json->GetBoolField(TEXT("value")); - - if (!DryRun) - { - FStaticParameterSet StaticParams; - MI->GetStaticParameterValues(StaticParams); - - bool bFound = false; - for (FStaticSwitchParameter& Param : StaticParams.StaticSwitchParameters) - { - if (Param.ParameterInfo.Name == FName(*ParameterName)) - { - Param.Value = bSwitchValue; - Param.bOverride = true; - bFound = true; - break; - } - } - - if (!bFound) - { - FStaticSwitchParameter NewParam; - NewParam.ParameterInfo.Name = FName(*ParameterName); - NewParam.Value = bSwitchValue; - NewParam.bOverride = true; - StaticParams.StaticSwitchParameters.Add(NewParam); - } - - MI->UpdateStaticPermutation(StaticParams); - } - NewValueDescription = bSwitchValue ? TEXT("true") : TEXT("false"); - } - else - { - Result.Appendf(TEXT("ERROR: Unknown parameter type '%s'. Valid types: scalar, vector, texture, staticSwitch\n"), *TypeStr); - return; - } - - if (!DryRun) - { - MCPUtils::PreEdit({MI}); - MCPUtils::PostEdit({MI}); - MCPUtils::SaveGenericPackage(MI); - } - - if (DryRun) - Result.Appendf(TEXT("[DRY RUN] Would set %s \"%s\" = %s on %s\n"), *TypeStr, *ParameterName, *NewValueDescription, *MCPUtils::FormatName(MI)); - else - Result.Appendf(TEXT("Set %s \"%s\" = %s on %s\n"), *TypeStr, *ParameterName, *NewValueDescription, *MCPUtils::FormatName(MI)); - } - -private: - // Auto-detect parameter type by examining the base material's expressions. - FString AutoDetectType(UMaterialInstanceConstant* MI) - { - UMaterial* BaseMat = MI->GetMaterial(); - if (!BaseMat) return FString(); - - for (UMaterialExpression* Expr : BaseMat->GetExpressions()) - { - if (!Expr) continue; - if (auto* SP = Cast(Expr)) - { - if (SP->ParameterName.ToString() == ParameterName) - return TEXT("scalar"); - } - else if (auto* VP = Cast(Expr)) - { - if (VP->ParameterName.ToString() == ParameterName) - return TEXT("vector"); - } - else if (auto* TP = Cast(Expr)) - { - if (TP->ParameterName.ToString() == ParameterName) - return TEXT("texture"); - } - else if (auto* SSP = Cast(Expr)) - { - if (SSP->ParameterName.ToString() == ParameterName) - return TEXT("staticSwitch"); - } - } - return FString(); - } -}; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp index ce2b56da..ea4e5df3 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp @@ -1150,21 +1150,79 @@ void MCPUtils::PostEdit(const TArray& Objects) GEditor->RedrawAllViewports(); } -TArray MCPUtils::MaterialParameters(UMaterialInstanceConstant *MI) + +TMap MCPUtils::GetMaterialParameters(UMaterialInterface* Material) { - TArray Results; - UMaterial* BaseMat = MI->GetMaterial(); - if (!BaseMat) return Results; - for (UMaterialExpression* Expr : BaseMat->GetExpressions()) + TMap Result; + if (!Material) return Result; + TMap Temp; + for (int32 i = 0; i < (int32)EMaterialParameterType::NumRuntime; i++) { - if (!Expr || !Expr->bIsParameterExpression) continue; - MCPMaterialParameter P; - P.Name = Expr->GetParameterName(); - if (P.Name.IsNone()) continue; - if (!Expr->GetParameterValue(P.Metadata)) continue; - Results.Add(P); + Material->GetAllParametersOfType((EMaterialParameterType)i, Temp); + Result.Append(Temp); + } + return Result; +} + +bool MCPUtils::ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation, MCPErrorCallback Error) +{ + if (Str.Equals(TEXT("Global"), ESearchCase::IgnoreCase)) + OutAssociation = GlobalParameter; + else if (Str.Equals(TEXT("Layer"), ESearchCase::IgnoreCase)) + OutAssociation = LayerParameter; + else if (Str.Equals(TEXT("Blend"), ESearchCase::IgnoreCase)) + OutAssociation = BlendParameter; + else + { + Error.SetError(FString::Printf(TEXT("Invalid ParameterAssociation '%s' (expected 'Global', 'Layer', or 'Blend')"), *Str)); + return false; + } + return true; +} + +void MCPUtils::FormatMaterialParameter(FStringBuilderBase& Result, const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta) +{ + // Association prefix for layer/blend parameters. + FString Prefix; + if (Info.Association == LayerParameter) + Prefix = FString::Printf(TEXT("[Layer %d] "), Info.Index); + else if (Info.Association == BlendParameter) + Prefix = FString::Printf(TEXT("[Blend %d] "), Info.Index); + + switch (Meta.Value.Type) + { + case EMaterialParameterType::Scalar: + Result.Appendf(TEXT(" %sScalar \"%s\" = %g\n"), *Prefix, *Info.Name.ToString(), Meta.Value.AsScalar()); + break; + case EMaterialParameterType::Vector: + { + FLinearColor C = Meta.Value.AsLinearColor(); + Result.Appendf(TEXT(" %sVector \"%s\" = (R=%.3f, G=%.3f, B=%.3f, A=%.3f)\n"), + *Prefix, *Info.Name.ToString(), C.R, C.G, C.B, C.A); + break; + } + case EMaterialParameterType::DoubleVector: + { + FVector4d V = Meta.Value.AsVector4d(); + Result.Appendf(TEXT(" %sDoubleVector \"%s\" = (%.3f, %.3f, %.3f, %.3f)\n"), + *Prefix, *Info.Name.ToString(), V.X, V.Y, V.Z, V.W); + break; + } + case EMaterialParameterType::Texture: + { + UTexture* Tex = Cast(Meta.Value.AsTextureObject()); + Result.Appendf(TEXT(" %sTexture \"%s\" = %s\n"), + *Prefix, *Info.Name.ToString(), Tex ? *MCPUtils::FormatName(Tex) : TEXT("None")); + break; + } + case EMaterialParameterType::StaticSwitch: + Result.Appendf(TEXT(" %sStaticSwitch \"%s\" = %s\n"), + *Prefix, *Info.Name.ToString(), Meta.Value.AsStaticSwitch() ? TEXT("true") : TEXT("false")); + break; + default: + Result.Appendf(TEXT(" %sType%d \"%s\"\n"), *Prefix, (int)Meta.Value.Type, *Info.Name.ToString()); + break; } - return Results; } bool MCPUtils::SaveMaterialPackage(UMaterial* Material) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h index 8c2a28e9..ed19a750 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPUtils.h @@ -98,12 +98,6 @@ struct MCPErrorCallback void SetError(const FString& Msg) const { Func(Msg); } }; -struct MCPMaterialParameter -{ - FName Name; - FMaterialParameterMetadata Metadata; -}; - // Stateless utility functions used by MCP handlers and the MCP server. // This is effectively a namespace — all methods are static. class MCPUtils @@ -285,8 +279,10 @@ public: static void PreEdit(const TArray& Objects); static void PostEdit(const TArray& Objects); - // ----- Material Instance ----- - static TArray MaterialParameters(UMaterialInstanceConstant *MI); + // ----- Material Parameters ----- + static TMap GetMaterialParameters(UMaterialInterface* Material); + static bool ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation, MCPErrorCallback Error = nullptr); + static void FormatMaterialParameter(FStringBuilderBase& Result, const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta); // ----- Editable template ----- // Given an object, returns the appropriate template object for generic