More work on handlers
This commit is contained in:
@@ -83,12 +83,12 @@ public:
|
|||||||
FBlendParameter& MutableParamY = const_cast<FBlendParameter&>(ParamY);
|
FBlendParameter& MutableParamY = const_cast<FBlendParameter&>(ParamY);
|
||||||
|
|
||||||
if (!AxisXName.IsEmpty()) MutableParamX.DisplayName = AxisXName;
|
if (!AxisXName.IsEmpty()) MutableParamX.DisplayName = AxisXName;
|
||||||
if (Json->HasField(TEXT("axisXMin"))) MutableParamX.Min = AxisXMin;
|
if (AxisXMin != 0.0f) MutableParamX.Min = AxisXMin;
|
||||||
if (Json->HasField(TEXT("axisXMax"))) MutableParamX.Max = AxisXMax;
|
if (AxisXMax != 0.0f) MutableParamX.Max = AxisXMax;
|
||||||
|
|
||||||
if (!AxisYName.IsEmpty()) MutableParamY.DisplayName = AxisYName;
|
if (!AxisYName.IsEmpty()) MutableParamY.DisplayName = AxisYName;
|
||||||
if (Json->HasField(TEXT("axisYMin"))) MutableParamY.Min = AxisYMin;
|
if (AxisYMin != 0.0f) MutableParamY.Min = AxisYMin;
|
||||||
if (Json->HasField(TEXT("axisYMax"))) MutableParamY.Max = AxisYMax;
|
if (AxisYMax != 0.0f) MutableParamY.Max = AxisYMax;
|
||||||
|
|
||||||
// Clear existing samples (delete from end to start)
|
// Clear existing samples (delete from end to start)
|
||||||
int32 NumExisting = BS->GetNumberOfBlendSamples();
|
int32 NumExisting = BS->GetNumberOfBlendSamples();
|
||||||
|
|||||||
@@ -82,8 +82,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply optional properties.
|
// Apply optional properties.
|
||||||
bool bHasTwoSided = Json->HasField(TEXT("twoSided"));
|
|
||||||
|
|
||||||
TArray<UObject*> Chain = { MaterialObj };
|
TArray<UObject*> Chain = { MaterialObj };
|
||||||
MCPUtils::PreEdit(Chain);
|
MCPUtils::PreEdit(Chain);
|
||||||
|
|
||||||
@@ -93,8 +91,7 @@ public:
|
|||||||
if (!BlendMode.IsEmpty())
|
if (!BlendMode.IsEmpty())
|
||||||
MaterialObj->BlendMode = ParsedBlendMode;
|
MaterialObj->BlendMode = ParsedBlendMode;
|
||||||
|
|
||||||
if (bHasTwoSided)
|
MaterialObj->TwoSided = TwoSided ? 1 : 0;
|
||||||
MaterialObj->TwoSided = TwoSided;
|
|
||||||
|
|
||||||
MCPUtils::PostEdit(Chain);
|
MCPUtils::PostEdit(Chain);
|
||||||
|
|
||||||
|
|||||||
@@ -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"));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
#include "MCPAssetFinder.h"
|
#include "MCPFetcher.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "Materials/Material.h"
|
#include "Materials/Material.h"
|
||||||
#include "MaterialDomain.h"
|
#include "MaterialDomain.h"
|
||||||
@@ -19,102 +19,81 @@ class UMCP_Material_SetProperty : public UObject, public IMCPHandler
|
|||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
public:
|
public:
|
||||||
UPROPERTY(meta=(Description="Material name or package path"))
|
UPROPERTY(meta=(Description="Material path"))
|
||||||
FString Material;
|
FString Path;
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="Property name to set (domain, blendMode, twoSided, shadingModel, opacity, opacityMaskClipValue, bUsedWithSkeletalMesh, bUsedWithMorphTargets, bUsedWithNiagaraSprites, ditheredLODTransition, bAllowNegativeEmissiveColor)"))
|
UPROPERTY(meta=(Description="Property name to set (domain, blendMode, twoSided, shadingModel, opacity, opacityMaskClipValue, bUsedWithSkeletalMesh, bUsedWithMorphTargets, bUsedWithNiagaraSprites, ditheredLODTransition, bAllowNegativeEmissiveColor)"))
|
||||||
FString Property;
|
FString Property;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="If true, preview the change without applying it"))
|
UPROPERTY(meta=(Description="Value to set"))
|
||||||
bool DryRun = false;
|
FString Value;
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
{
|
{
|
||||||
return TEXT("Set a top-level material property such as domain, blend mode, shading model, or usage flags. "
|
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.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
||||||
{
|
{
|
||||||
if (!Json->HasField(TEXT("value")))
|
MCPFetcher F(Result);
|
||||||
{
|
UMaterial* Mat = F.Asset(Path).Cast<UMaterial>();
|
||||||
Result.Append(TEXT("ERROR: Missing required field: value\n"));
|
if (!Mat) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load material
|
// Parse value and build setter. Validation happens here so we can bail before PreEdit.
|
||||||
MCPAssets<UMaterial> Assets;
|
TFunction<void()> Setter;
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Property == TEXT("domain"))
|
if (Property == TEXT("domain"))
|
||||||
{
|
{
|
||||||
FString ValueStr = Json->GetStringField(TEXT("value"));
|
|
||||||
OldValue = MCPUtils::EnumToString(Mat->MaterialDomain, TEXT("MD_"));
|
|
||||||
EMaterialDomain NewDomain;
|
EMaterialDomain NewDomain;
|
||||||
if (!MCPUtils::StringToEnum(ValueStr, NewDomain, Result, TEXT("MD_"))) return;
|
if (!MCPUtils::StringToEnum(Value, NewDomain, Result, TEXT("MD_"))) return;
|
||||||
NewValue = MCPUtils::EnumToString(NewDomain, TEXT("MD_"));
|
Setter = [Mat, NewDomain]() { Mat->MaterialDomain = NewDomain; };
|
||||||
if (!DryRun) Mat->MaterialDomain = NewDomain;
|
|
||||||
}
|
}
|
||||||
else if (Property == TEXT("blendMode"))
|
else if (Property == TEXT("blendMode"))
|
||||||
{
|
{
|
||||||
FString ValueStr = Json->GetStringField(TEXT("value"));
|
|
||||||
OldValue = MCPUtils::EnumToString(Mat->BlendMode, TEXT("BLEND_"));
|
|
||||||
EBlendMode NewBlend;
|
EBlendMode NewBlend;
|
||||||
if (!MCPUtils::StringToEnum(ValueStr, NewBlend, Result, TEXT("BLEND_"))) return;
|
if (!MCPUtils::StringToEnum(Value, NewBlend, Result, TEXT("BLEND_"))) return;
|
||||||
NewValue = MCPUtils::EnumToString(NewBlend, TEXT("BLEND_"));
|
Setter = [Mat, NewBlend]() { Mat->BlendMode = NewBlend; };
|
||||||
if (!DryRun) Mat->BlendMode = NewBlend;
|
|
||||||
}
|
}
|
||||||
else if (Property == TEXT("shadingModel"))
|
else if (Property == TEXT("shadingModel"))
|
||||||
{
|
{
|
||||||
FString ValueStr = Json->GetStringField(TEXT("value"));
|
|
||||||
OldValue = MCPUtils::EnumToString(Mat->GetShadingModels().GetFirstShadingModel(), TEXT("MSM_"));
|
|
||||||
EMaterialShadingModel NewModel;
|
EMaterialShadingModel NewModel;
|
||||||
if (!MCPUtils::StringToEnum(ValueStr, NewModel, Result, TEXT("MSM_"))) return;
|
if (!MCPUtils::StringToEnum(Value, NewModel, Result, TEXT("MSM_"))) return;
|
||||||
NewValue = MCPUtils::EnumToString(NewModel, TEXT("MSM_"));
|
Setter = [Mat, NewModel]() { Mat->SetShadingModel(NewModel); };
|
||||||
if (!DryRun) Mat->SetShadingModel(NewModel);
|
|
||||||
}
|
}
|
||||||
else if (Property == TEXT("opacity") || Property == TEXT("opacityMaskClipValue"))
|
else if (Property == TEXT("opacity") || Property == TEXT("opacityMaskClipValue"))
|
||||||
{
|
{
|
||||||
double OpacityValue = Json->GetNumberField(TEXT("value"));
|
float FloatVal = FCString::Atof(*Value);
|
||||||
OldValue = FString::Printf(TEXT("%g"), Mat->OpacityMaskClipValue);
|
Setter = [Mat, FloatVal]() { Mat->OpacityMaskClipValue = FloatVal; };
|
||||||
NewValue = FString::Printf(TEXT("%g"), OpacityValue);
|
|
||||||
if (!DryRun) Mat->OpacityMaskClipValue = (float)OpacityValue;
|
|
||||||
}
|
}
|
||||||
else if (Property == TEXT("twoSided"))
|
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"))
|
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"))
|
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"))
|
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"))
|
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"))
|
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
|
else
|
||||||
{
|
{
|
||||||
@@ -124,18 +103,12 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notify and save if not dry run
|
// Apply the change between PreEdit/PostEdit.
|
||||||
if (!DryRun)
|
F.PreEdit();
|
||||||
{
|
Setter();
|
||||||
TArray<UObject*> Chain = { Mat };
|
F.PostEdit();
|
||||||
MCPUtils::PreEdit(Chain);
|
MCPUtils::SaveMaterialPackage(Mat);
|
||||||
MCPUtils::PostEdit(Chain);
|
|
||||||
if (!MCPUtils::SaveMaterialPackage(Mat))
|
|
||||||
Result.Append(TEXT("WARNING: Package save failed\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Result.Appendf(TEXT("%s%s: %s -> %s\n"),
|
Result.Appendf(TEXT("Set %s on %s\n"), *Property, *MCPUtils::FormatName(Mat));
|
||||||
DryRun ? TEXT("[DRY RUN] ") : TEXT(""),
|
|
||||||
*Property, *OldValue, *NewValue);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -84,18 +84,9 @@ public:
|
|||||||
TransNode->CreateConnections(FromStateNode, ToStateNode);
|
TransNode->CreateConnections(FromStateNode, ToStateNode);
|
||||||
|
|
||||||
// Set optional properties
|
// Set optional properties
|
||||||
if (Json->HasField(TEXT("crossfadeDuration")))
|
|
||||||
{
|
|
||||||
TransNode->CrossfadeDuration = CrossfadeDuration;
|
TransNode->CrossfadeDuration = CrossfadeDuration;
|
||||||
}
|
|
||||||
if (Json->HasField(TEXT("priority")))
|
|
||||||
{
|
|
||||||
TransNode->PriorityOrder = Priority;
|
TransNode->PriorityOrder = Priority;
|
||||||
}
|
|
||||||
if (Json->HasField(TEXT("bBidirectional")))
|
|
||||||
{
|
|
||||||
TransNode->Bidirectional = BBidirectional;
|
TransNode->Bidirectional = BBidirectional;
|
||||||
}
|
|
||||||
|
|
||||||
// Compile and save
|
// Compile and save
|
||||||
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
||||||
|
|||||||
@@ -74,40 +74,12 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count and apply property changes
|
// Apply properties
|
||||||
int32 ChangedCount = 0;
|
|
||||||
|
|
||||||
if (Json->HasField(TEXT("crossfadeDuration")))
|
|
||||||
{
|
|
||||||
TransNode->CrossfadeDuration = CrossfadeDuration;
|
TransNode->CrossfadeDuration = CrossfadeDuration;
|
||||||
ChangedCount++;
|
|
||||||
}
|
|
||||||
if (Json->HasField(TEXT("blendMode")))
|
|
||||||
{
|
|
||||||
TransNode->BlendMode = (EAlphaBlendOption)BlendMode;
|
TransNode->BlendMode = (EAlphaBlendOption)BlendMode;
|
||||||
ChangedCount++;
|
|
||||||
}
|
|
||||||
if (Json->HasField(TEXT("priorityOrder")))
|
|
||||||
{
|
|
||||||
TransNode->PriorityOrder = PriorityOrder;
|
TransNode->PriorityOrder = PriorityOrder;
|
||||||
ChangedCount++;
|
|
||||||
}
|
|
||||||
if (Json->HasField(TEXT("logicType")))
|
|
||||||
{
|
|
||||||
TransNode->LogicType = (ETransitionLogicType::Type)LogicType;
|
TransNode->LogicType = (ETransitionLogicType::Type)LogicType;
|
||||||
ChangedCount++;
|
|
||||||
}
|
|
||||||
if (Json->HasField(TEXT("bBidirectional")))
|
|
||||||
{
|
|
||||||
TransNode->Bidirectional = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
TArray<UObject*> Chain = { TransNode };
|
TArray<UObject*> Chain = { TransNode };
|
||||||
MCPUtils::PreEdit(Chain);
|
MCPUtils::PreEdit(Chain);
|
||||||
@@ -117,7 +89,7 @@ public:
|
|||||||
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
FKismetEditorUtilities::CompileBlueprint(AnimBP);
|
||||||
MCPUtils::SaveBlueprintPackage(AnimBP);
|
MCPUtils::SaveBlueprintPackage(AnimBP);
|
||||||
|
|
||||||
Result.Appendf(TEXT("Updated %d properties on transition %s -> %s: %s\n"),
|
Result.Appendf(TEXT("Updated transition %s -> %s: %s\n"),
|
||||||
ChangedCount, *FromState, *ToState, *MCPUtils::FormatName(TransNode));
|
*FromState, *ToState, *MCPUtils::FormatName(TransNode));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -218,6 +218,14 @@ public:
|
|||||||
return true;
|
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 -----
|
// ----- Blueprint helpers -----
|
||||||
static TArray<UEdGraph*> AllGraphs(UBlueprint* BP);
|
static TArray<UEdGraph*> AllGraphs(UBlueprint* BP);
|
||||||
static TArray<UEdGraph*> AllGraphsNamed(UBlueprint* BP, const FString& Name);
|
static TArray<UEdGraph*> AllGraphsNamed(UBlueprint* BP, const FString& Name);
|
||||||
|
|||||||
Reference in New Issue
Block a user