BlueprintVariable handlers
This commit is contained in:
Binary file not shown.
BIN
Content/Testing/BP_VarTest2.uasset
LFS
Normal file
BIN
Content/Testing/BP_VarTest2.uasset
LFS
Normal file
Binary file not shown.
@@ -1,190 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "MCPHandler.h"
|
|
||||||
#include "MCPFetcher.h"
|
|
||||||
#include "MCPUtils.h"
|
|
||||||
#include "Engine/Blueprint.h"
|
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
|
||||||
#include "Blueprint_SetVariableMetadata.generated.h"
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
UCLASS()
|
|
||||||
class UMCP_Blueprint_SetVariableMetadata : public UObject, public IMCPHandler
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
|
|
||||||
public:
|
|
||||||
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
|
||||||
FString Blueprint;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="Name of the variable to modify"))
|
|
||||||
FString Variable;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Category to assign the variable to"))
|
|
||||||
FString Category;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Tooltip text for the variable"))
|
|
||||||
FString Tooltip;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Replication mode: none, replicated, or repNotify"))
|
|
||||||
FString Replication;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="If true, expose this variable on spawn"))
|
|
||||||
bool ExposeOnSpawn = false;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="If true, mark the variable as private"))
|
|
||||||
bool IsPrivate = false;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Editability mode: editAnywhere, editDefaultsOnly, editInstanceOnly, or none"))
|
|
||||||
FString Editability;
|
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
|
||||||
{
|
|
||||||
return TEXT("Set variable metadata properties such as category, tooltip, "
|
|
||||||
"replication, editability, and visibility flags.");
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override
|
|
||||||
{
|
|
||||||
MCPFetcher F(Result);
|
|
||||||
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
|
|
||||||
if (!BP) return;
|
|
||||||
|
|
||||||
// Find the variable using Identifies for consistent name matching
|
|
||||||
FBPVariableDescription* VarDesc = nullptr;
|
|
||||||
for (FBPVariableDescription& Var : BP->NewVariables)
|
|
||||||
{
|
|
||||||
if (MCPUtils::FormatName(Var) == Variable ||
|
|
||||||
Var.VarName.ToString().Equals(Variable, ESearchCase::IgnoreCase))
|
|
||||||
{
|
|
||||||
VarDesc = &Var;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!VarDesc)
|
|
||||||
{
|
|
||||||
Result.Appendf(TEXT("ERROR: Variable '%s' not found in %s.\nAvailable variables:\n"),
|
|
||||||
*Variable, *MCPUtils::FormatName(BP));
|
|
||||||
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
||||||
{
|
|
||||||
Result.Appendf(TEXT(" %s\n"), *MCPUtils::FormatName(Var));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FName VarFName = VarDesc->VarName;
|
|
||||||
int32 ChangeCount = 0;
|
|
||||||
F.PreEdit();
|
|
||||||
|
|
||||||
// Category
|
|
||||||
if (Json->HasField(TEXT("category")))
|
|
||||||
{
|
|
||||||
VarDesc->Category = FText::FromString(Category);
|
|
||||||
FBlueprintEditorUtils::SetBlueprintVariableCategory(BP, VarFName, nullptr, FText::FromString(Category));
|
|
||||||
Result.Appendf(TEXT("Set category to '%s'.\n"), *Category);
|
|
||||||
ChangeCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tooltip
|
|
||||||
if (Json->HasField(TEXT("tooltip")))
|
|
||||||
{
|
|
||||||
FBlueprintEditorUtils::SetBlueprintVariableMetaData(BP, VarFName, nullptr, TEXT("tooltip"), Tooltip);
|
|
||||||
Result.Appendf(TEXT("Set tooltip to '%s'.\n"), *Tooltip);
|
|
||||||
ChangeCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replication
|
|
||||||
if (Json->HasField(TEXT("replication")))
|
|
||||||
{
|
|
||||||
if (Replication == TEXT("none"))
|
|
||||||
{
|
|
||||||
VarDesc->PropertyFlags &= ~CPF_Net;
|
|
||||||
VarDesc->PropertyFlags &= ~CPF_RepNotify;
|
|
||||||
VarDesc->RepNotifyFunc = NAME_None;
|
|
||||||
}
|
|
||||||
else if (Replication == TEXT("replicated"))
|
|
||||||
{
|
|
||||||
VarDesc->PropertyFlags |= CPF_Net;
|
|
||||||
VarDesc->PropertyFlags &= ~CPF_RepNotify;
|
|
||||||
VarDesc->RepNotifyFunc = NAME_None;
|
|
||||||
}
|
|
||||||
else if (Replication == TEXT("repNotify"))
|
|
||||||
{
|
|
||||||
VarDesc->PropertyFlags |= CPF_Net | CPF_RepNotify;
|
|
||||||
VarDesc->RepNotifyFunc = FName(*FString::Printf(TEXT("OnRep_%s"), *Variable));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Result.Appendf(TEXT("ERROR: Invalid replication value '%s'. Valid: none, replicated, repNotify\n"), *Replication);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Result.Appendf(TEXT("Set replication to '%s'.\n"), *Replication);
|
|
||||||
ChangeCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExposeOnSpawn
|
|
||||||
if (Json->HasField(TEXT("exposeOnSpawn")))
|
|
||||||
{
|
|
||||||
if (ExposeOnSpawn)
|
|
||||||
VarDesc->PropertyFlags |= CPF_ExposeOnSpawn;
|
|
||||||
else
|
|
||||||
VarDesc->PropertyFlags &= ~CPF_ExposeOnSpawn;
|
|
||||||
Result.Appendf(TEXT("Set exposeOnSpawn to %s.\n"), ExposeOnSpawn ? TEXT("true") : TEXT("false"));
|
|
||||||
ChangeCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// isPrivate
|
|
||||||
if (Json->HasField(TEXT("isPrivate")))
|
|
||||||
{
|
|
||||||
FBlueprintEditorUtils::SetBlueprintVariableMetaData(BP, VarFName, nullptr,
|
|
||||||
TEXT("BlueprintPrivate"), IsPrivate ? TEXT("true") : TEXT("false"));
|
|
||||||
Result.Appendf(TEXT("Set isPrivate to %s.\n"), IsPrivate ? TEXT("true") : TEXT("false"));
|
|
||||||
ChangeCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Editability
|
|
||||||
if (Json->HasField(TEXT("editability")))
|
|
||||||
{
|
|
||||||
VarDesc->PropertyFlags &= ~(CPF_Edit | CPF_DisableEditOnInstance | CPF_DisableEditOnTemplate);
|
|
||||||
|
|
||||||
if (Editability == TEXT("editAnywhere"))
|
|
||||||
{
|
|
||||||
VarDesc->PropertyFlags |= CPF_Edit;
|
|
||||||
}
|
|
||||||
else if (Editability == TEXT("editDefaultsOnly"))
|
|
||||||
{
|
|
||||||
VarDesc->PropertyFlags |= CPF_Edit | CPF_DisableEditOnInstance;
|
|
||||||
}
|
|
||||||
else if (Editability == TEXT("editInstanceOnly"))
|
|
||||||
{
|
|
||||||
VarDesc->PropertyFlags |= CPF_Edit | CPF_DisableEditOnTemplate;
|
|
||||||
}
|
|
||||||
else if (Editability != TEXT("none"))
|
|
||||||
{
|
|
||||||
Result.Appendf(TEXT("ERROR: Invalid editability value '%s'. Valid: editAnywhere, editDefaultsOnly, editInstanceOnly, none\n"), *Editability);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Result.Appendf(TEXT("Set editability to '%s'.\n"), *Editability);
|
|
||||||
ChangeCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ChangeCount == 0)
|
|
||||||
{
|
|
||||||
Result.Append(TEXT("ERROR: No metadata fields specified. Provide at least one of: category, tooltip, replication, exposeOnSpawn, isPrivate, editability\n"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
F.PostEdit();
|
|
||||||
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
|
|
||||||
|
|
||||||
Result.Appendf(TEXT("Updated %d field(s) on %s in %s.%s\n"),
|
|
||||||
ChangeCount, *VarFName.ToString(), *MCPUtils::FormatName(BP),
|
|
||||||
bSaved ? TEXT("") : TEXT(" WARNING: save failed."));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,93 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "MCPHandler.h"
|
|
||||||
#include "MCPFetcher.h"
|
|
||||||
#include "MCPTypes.h"
|
|
||||||
#include "MCPUtils.h"
|
|
||||||
#include "MCPServer.h"
|
|
||||||
#include "Engine/Blueprint.h"
|
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
|
||||||
#include "Blueprint_AddVariable.generated.h"
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
UCLASS()
|
|
||||||
class UMCP_Blueprint_AddVariable : public UObject, public IMCPHandler
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
|
|
||||||
public:
|
|
||||||
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
|
||||||
FString Blueprint;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="Name of the new variable"))
|
|
||||||
FString VariableName;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="Type of the new variable"))
|
|
||||||
FString VariableType;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Category to assign the variable to"))
|
|
||||||
FString Category;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="If true, make the variable an array"))
|
|
||||||
bool IsArray = false;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Default value for the variable"))
|
|
||||||
FString DefaultValue;
|
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
|
||||||
{
|
|
||||||
return TEXT("Add a new member variable to a Blueprint.");
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Handle() override
|
|
||||||
{
|
|
||||||
MCPFetcher F;
|
|
||||||
UBlueprint* BP = F.Asset(Blueprint).Cast<UBlueprint>();
|
|
||||||
if (!BP) return;
|
|
||||||
|
|
||||||
// Check for duplicate variable name
|
|
||||||
FName VarFName(*VariableName);
|
|
||||||
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
||||||
{
|
|
||||||
if (Var.VarName == VarFName)
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: Variable '%s' already exists in %s\n"), *VariableName, *MCPUtils::FormatName(BP));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve the type
|
|
||||||
FEdGraphPinType PinType;
|
|
||||||
if (!UMCPTypes::TextToType(VariableType, PinType))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (IsArray)
|
|
||||||
PinType.ContainerType = EPinContainerType::Array;
|
|
||||||
|
|
||||||
// Add the variable
|
|
||||||
if (!FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, PinType, DefaultValue))
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: Failed to add variable '%s' to %s\n"), *VariableName, *MCPUtils::FormatName(BP));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Category.IsEmpty())
|
|
||||||
FBlueprintEditorUtils::SetBlueprintVariableCategory(BP, VarFName, nullptr, FText::FromString(Category));
|
|
||||||
|
|
||||||
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
|
|
||||||
|
|
||||||
UMCPServer::Printf(TEXT("Added %s %s to %s\n"),
|
|
||||||
*VariableType, *VariableName, *MCPUtils::FormatName(BP));
|
|
||||||
if (IsArray)
|
|
||||||
UMCPServer::Print(TEXT("Container: Array\n"));
|
|
||||||
if (!Category.IsEmpty())
|
|
||||||
UMCPServer::Printf(TEXT("Category: %s\n"), *Category);
|
|
||||||
if (!bSaved)
|
|
||||||
UMCPServer::Print(TEXT("Warning: package save failed\n"));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "MCPServer.h"
|
|
||||||
#include "MCPTypes.h"
|
|
||||||
#include "MCPHandler.h"
|
|
||||||
#include "MCPFetcher.h"
|
|
||||||
#include "MCPUtils.h"
|
|
||||||
#include "Engine/Blueprint.h"
|
|
||||||
#include "EdGraph/EdGraphPin.h"
|
|
||||||
#include "K2Node_VariableGet.h"
|
|
||||||
#include "K2Node_VariableSet.h"
|
|
||||||
#include "Blueprint_ChangeVariableType.generated.h"
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
UCLASS()
|
|
||||||
class UMCP_Blueprint_ChangeVariableType : public UObject, public IMCPHandler
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
|
|
||||||
public:
|
|
||||||
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
|
||||||
FString Blueprint;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="Name of the variable to change"))
|
|
||||||
FString Variable;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="New type name for the variable"))
|
|
||||||
FString NewType;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="Type category: object, softobject, class, softclass, interface, struct, enum"))
|
|
||||||
FString TypeCategory;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="If true, analyze the change without applying it"))
|
|
||||||
bool DryRun = false;
|
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
|
||||||
{
|
|
||||||
return TEXT("Change the type of a Blueprint member variable. "
|
|
||||||
"Supports dry-run mode to preview affected nodes before committing.");
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Handle() override
|
|
||||||
{
|
|
||||||
MCPFetcher F;
|
|
||||||
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
|
|
||||||
if (!BP) return;
|
|
||||||
|
|
||||||
// Find the variable
|
|
||||||
FBPVariableDescription* Found = nullptr;
|
|
||||||
for (FBPVariableDescription& Var : BP->NewVariables)
|
|
||||||
{
|
|
||||||
if (MCPUtils::FormatName(Var) == Variable ||
|
|
||||||
Var.VarName.ToString().Equals(Variable, ESearchCase::IgnoreCase))
|
|
||||||
{
|
|
||||||
Found = &Var;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!Found)
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: Variable '%s' not found in %s.\nAvailable variables:\n"),
|
|
||||||
*Variable, *MCPUtils::FormatName(BP));
|
|
||||||
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
||||||
UMCPServer::Printf(TEXT(" %s\n"), *MCPUtils::FormatName(Var));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the new pin type using shared resolver
|
|
||||||
FEdGraphPinType NewPinType;
|
|
||||||
FString ResolveInput = NewType;
|
|
||||||
|
|
||||||
// If typeCategory is an object reference variant, use colon syntax for the resolver
|
|
||||||
if (TypeCategory == TEXT("object") || TypeCategory == TEXT("softobject") ||
|
|
||||||
TypeCategory == TEXT("class") || TypeCategory == TEXT("softclass") ||
|
|
||||||
TypeCategory == TEXT("interface"))
|
|
||||||
{
|
|
||||||
ResolveInput = TypeCategory + TEXT(":") + NewType;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!UMCPTypes::TextToType(ResolveInput, NewPinType))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// List affected nodes (get/set nodes for this variable)
|
|
||||||
FName VarFName = Found->VarName;
|
|
||||||
auto AppendAffectedNodes = [&](const auto& NodeArray, const TCHAR* NodeType)
|
|
||||||
{
|
|
||||||
for (auto* VarNode : NodeArray)
|
|
||||||
{
|
|
||||||
if (VarNode->GetVarName() != VarFName) continue;
|
|
||||||
UMCPServer::Printf(TEXT(" %s %s in %s\n"), NodeType,
|
|
||||||
*MCPUtils::FormatName(static_cast<UEdGraphNode*>(VarNode)),
|
|
||||||
*MCPUtils::FormatName(VarNode->GetGraph()));
|
|
||||||
for (UEdGraphPin* Pin : VarNode->Pins)
|
|
||||||
{
|
|
||||||
if (!Pin || Pin->LinkedTo.Num() == 0) continue;
|
|
||||||
if (NodeType[0] == 'G' && Pin->Direction != EGPD_Output) continue; // Get nodes: only output pins
|
|
||||||
UMCPServer::Printf(TEXT(" %s connected to %d pin(s)\n"),
|
|
||||||
*MCPUtils::FormatName(Pin), Pin->LinkedTo.Num());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
auto GetNodes = MCPUtils::AllNodes<UK2Node_VariableGet>(BP);
|
|
||||||
auto SetNodes = MCPUtils::AllNodes<UK2Node_VariableSet>(BP);
|
|
||||||
|
|
||||||
bool bHasAffected = false;
|
|
||||||
for (auto* VG : GetNodes) if (VG->GetVarName() == VarFName) { bHasAffected = true; break; }
|
|
||||||
if (!bHasAffected)
|
|
||||||
for (auto* VS : SetNodes) if (VS->GetVarName() == VarFName) { bHasAffected = true; break; }
|
|
||||||
|
|
||||||
if (DryRun)
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("Dry run: would change %s from %s to %s\n"),
|
|
||||||
*MCPUtils::FormatName(*Found),
|
|
||||||
*UMCPTypes::TypeToText(Found->VarType),
|
|
||||||
*UMCPTypes::TypeToText(NewPinType));
|
|
||||||
if (bHasAffected)
|
|
||||||
{
|
|
||||||
UMCPServer::Print(TEXT("Affected nodes:\n"));
|
|
||||||
AppendAffectedNodes(GetNodes, TEXT("Get"));
|
|
||||||
AppendAffectedNodes(SetNodes, TEXT("Set"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply the type change
|
|
||||||
Found->VarType = NewPinType;
|
|
||||||
|
|
||||||
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
|
|
||||||
UMCPServer::Printf(TEXT("Changed %s to %s.%s\n"),
|
|
||||||
*MCPUtils::FormatName(*Found),
|
|
||||||
*UMCPTypes::TypeToText(NewPinType),
|
|
||||||
bSaved ? TEXT("") : TEXT(" WARNING: save failed."));
|
|
||||||
|
|
||||||
if (bHasAffected)
|
|
||||||
{
|
|
||||||
UMCPServer::Print(TEXT("Affected nodes:\n"));
|
|
||||||
AppendAffectedNodes(GetNodes, TEXT("Get"));
|
|
||||||
AppendAffectedNodes(SetNodes, TEXT("Set"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
|
||||||
#include "MCPServer.h"
|
|
||||||
#include "MCPHandler.h"
|
|
||||||
#include "MCPFetcher.h"
|
|
||||||
#include "MCPUtils.h"
|
|
||||||
#include "Engine/Blueprint.h"
|
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
|
||||||
#include "Blueprint_RemoveVariable.generated.h"
|
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
UCLASS()
|
|
||||||
class UMCP_Blueprint_RemoveVariable : public UObject, public IMCPHandler
|
|
||||||
{
|
|
||||||
GENERATED_BODY()
|
|
||||||
|
|
||||||
public:
|
|
||||||
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
|
||||||
FString Blueprint;
|
|
||||||
|
|
||||||
UPROPERTY(meta=(Description="Name of the variable to remove"))
|
|
||||||
FString VariableName;
|
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
|
||||||
{
|
|
||||||
return TEXT("Remove a member variable from a Blueprint.");
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void Handle() override
|
|
||||||
{
|
|
||||||
MCPFetcher F;
|
|
||||||
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
|
|
||||||
if (!BP) return;
|
|
||||||
|
|
||||||
// Find the variable using Identifies for consistent name matching
|
|
||||||
const FBPVariableDescription* Found = nullptr;
|
|
||||||
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
||||||
{
|
|
||||||
if (MCPUtils::FormatName(Var) == VariableName ||
|
|
||||||
Var.VarName.ToString().Equals(VariableName, ESearchCase::IgnoreCase))
|
|
||||||
{
|
|
||||||
Found = &Var;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Found)
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: Variable '%s' not found in %s.\nAvailable variables:\n"),
|
|
||||||
*VariableName, *MCPUtils::FormatName(BP));
|
|
||||||
for (const FBPVariableDescription& Var : BP->NewVariables)
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT(" %s\n"), *MCPUtils::FormatName(Var));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FName VarFName = Found->VarName;
|
|
||||||
|
|
||||||
// RemoveMemberVariable also cleans up Get/Set nodes
|
|
||||||
FBlueprintEditorUtils::RemoveMemberVariable(BP, VarFName);
|
|
||||||
|
|
||||||
bool bSaved = MCPUtils::SaveBlueprintPackage(BP);
|
|
||||||
UMCPServer::Printf(TEXT("Removed variable %s from %s.%s\n"),
|
|
||||||
*VarFName.ToString(), *MCPUtils::FormatName(BP),
|
|
||||||
bSaved ? TEXT("") : TEXT(" WARNING: save failed."));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPServer.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
|
#include "MCPProperty.h"
|
||||||
|
#include "BPVarEditor.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
|
#include "MCPTypes.h"
|
||||||
|
#include "Engine/Blueprint.h"
|
||||||
|
#include "EdGraphSchema_K2.h"
|
||||||
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
|
#include "BlueprintVariable_Create.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCP_BlueprintVariable_Create : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
||||||
|
FString Blueprint;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Name of the new variable"))
|
||||||
|
FString Name;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Optional, Description="Variable configuration: VarType, Category, DefaultValue, InstanceEditable, BlueprintReadOnly, ExposeOnSpawn, Private, ExposeToCinematics, etc."))
|
||||||
|
FMCPJsonObject Config;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Add a new member variable to a Blueprint. Pass Config to set type, category, flags, etc.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle() override
|
||||||
|
{
|
||||||
|
MCPFetcher F;
|
||||||
|
UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast<UBlueprint>();
|
||||||
|
if (!BP) return;
|
||||||
|
|
||||||
|
// Check for duplicate variable name
|
||||||
|
FName VarFName(*Name);
|
||||||
|
if (FBlueprintEditorUtils::FindNewVariableIndex(BP, VarFName) != INDEX_NONE)
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: Variable '%s' already exists in %s\n"), *Name, *MCPUtils::FormatName(BP));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the variable with a default type
|
||||||
|
FEdGraphPinType DefaultType;
|
||||||
|
DefaultType.PinCategory = UEdGraphSchema_K2::PC_Int;
|
||||||
|
if (!FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, DefaultType))
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: Failed to add variable '%s' to %s\n"), *Name, *MCPUtils::FormatName(BP));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the newly created variable description
|
||||||
|
FBPVarEditor Editor(BP, Name);
|
||||||
|
if (Editor.NotFound()) return;
|
||||||
|
|
||||||
|
// Apply config if provided
|
||||||
|
if (Config.Json && Config.Json->Values.Num() > 0)
|
||||||
|
{
|
||||||
|
if (!Editor.LoadJson(Config.Json.Get()))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UMCPServer::Printf(TEXT("Created variable %s (%s) in %s\n"),
|
||||||
|
*Name, *UMCPTypes::TypeToText(Editor.Desc->VarType), *MCPUtils::FormatName(BP));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPServer.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
|
#include "BPVarEditor.h"
|
||||||
|
#include "Engine/Blueprint.h"
|
||||||
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
|
#include "BlueprintVariable_Delete.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCP_BlueprintVariable_Delete : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
||||||
|
FString Blueprint;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Name of the variable to delete"))
|
||||||
|
FString Variable;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Remove a member variable from a Blueprint.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle() override
|
||||||
|
{
|
||||||
|
MCPFetcher F;
|
||||||
|
UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast<UBlueprint>();
|
||||||
|
if (!BP) return;
|
||||||
|
|
||||||
|
FBPVarEditor Editor(BP, Variable);
|
||||||
|
if (Editor.NotFound()) return;
|
||||||
|
|
||||||
|
FBlueprintEditorUtils::RemoveMemberVariable(BP, Editor.Desc->VarName);
|
||||||
|
|
||||||
|
UMCPServer::Printf(TEXT("Removed variable %s from %s\n"),
|
||||||
|
*Variable, *MCPUtils::FormatName(BP));
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPServer.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
|
#include "BPVarEditor.h"
|
||||||
|
#include "Engine/Blueprint.h"
|
||||||
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
|
#include "BlueprintVariable_Dump.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCP_BlueprintVariable_Dump : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
||||||
|
FString Blueprint;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Name of the variable to inspect"))
|
||||||
|
FString Variable;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Show all editable properties of a Blueprint variable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle() override
|
||||||
|
{
|
||||||
|
MCPFetcher F;
|
||||||
|
UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast<UBlueprint>();
|
||||||
|
if (!BP) return;
|
||||||
|
|
||||||
|
FBPVarEditor Editor(BP, Variable);
|
||||||
|
if (Editor.NotFound()) return;
|
||||||
|
|
||||||
|
UMCPServer::Printf(TEXT("Variable %s in %s:\n"), *Variable, *MCPUtils::FormatName(BP));
|
||||||
|
Editor.Dump();
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPServer.h"
|
||||||
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPFetcher.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
|
#include "MCPProperty.h"
|
||||||
|
#include "BPVarEditor.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
|
#include "MCPTypes.h"
|
||||||
|
#include "Engine/Blueprint.h"
|
||||||
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
|
#include "BlueprintVariable_Modify.generated.h"
|
||||||
|
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
UCLASS()
|
||||||
|
class UMCP_BlueprintVariable_Modify : public UObject, public IMCPHandler
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
public:
|
||||||
|
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
||||||
|
FString Blueprint;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Name of the variable to modify"))
|
||||||
|
FString Variable;
|
||||||
|
|
||||||
|
UPROPERTY(meta=(Description="Properties to change: VarType, Category, DefaultValue, InstanceEditable, BlueprintReadOnly, ExposeOnSpawn, Private, ExposeToCinematics, etc."))
|
||||||
|
FMCPJsonObject Properties;
|
||||||
|
|
||||||
|
virtual FString GetDescription() const override
|
||||||
|
{
|
||||||
|
return TEXT("Modify properties of an existing Blueprint variable.");
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void Handle() override
|
||||||
|
{
|
||||||
|
MCPFetcher F;
|
||||||
|
UBlueprint* BP = F.Walk(Blueprint).ToBlueprint().Cast<UBlueprint>();
|
||||||
|
if (!BP) return;
|
||||||
|
|
||||||
|
FBPVarEditor Editor(BP, Variable);
|
||||||
|
if (Editor.NotFound()) return;
|
||||||
|
|
||||||
|
if (!Properties.Json || Properties.Json->Values.Num() == 0)
|
||||||
|
{
|
||||||
|
UMCPServer::Print(TEXT("ERROR: No properties specified\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Editor.LoadJson(Properties.Json.Get()))
|
||||||
|
return;
|
||||||
|
|
||||||
|
UMCPServer::Printf(TEXT("Modified variable %s (%s) in %s\n"),
|
||||||
|
*Variable, *UMCPTypes::TypeToText(Editor.Desc->VarType), *MCPUtils::FormatName(BP));
|
||||||
|
}
|
||||||
|
};
|
||||||
110
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BPVarEditor.cpp
Normal file
110
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BPVarEditor.cpp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
#include "BPVarEditor.h"
|
||||||
|
#include "MCPJson.h"
|
||||||
|
#include "MCPServer.h"
|
||||||
|
#include "MCPTypes.h"
|
||||||
|
#include "MCPUtils.h"
|
||||||
|
#include "EdGraphSchema_K2.h"
|
||||||
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
|
|
||||||
|
FBPVarEditor::FBPVarEditor(UBlueprint* BP, const FString& VarName)
|
||||||
|
{
|
||||||
|
FName VarFName(*VarName);
|
||||||
|
int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(BP, VarFName);
|
||||||
|
if (VarIndex == INDEX_NONE)
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: Variable '%s' not found in %s\n"), *VarName, *MCPUtils::FormatName(BP));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Desc = &BP->NewVariables[VarIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
void FBPVarEditor::Dump()
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
TArray<MCPProperty> Props = MergedProperties();
|
||||||
|
for (MCPProperty& P : Props)
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT(" %s %s = %s\n"),
|
||||||
|
*UMCPTypes::TypeToText(P.Prop),
|
||||||
|
*MCPUtils::FormatName(P.Prop),
|
||||||
|
*P.GetText());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FBPVarEditor::LoadJson(const FJsonObject* Json)
|
||||||
|
{
|
||||||
|
Load();
|
||||||
|
TArray<MCPProperty> Props = MergedProperties();
|
||||||
|
if (!MCPJson::PopulateFromJson(Props, Json, true))
|
||||||
|
return false;
|
||||||
|
Save();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FBPVarEditor::Load()
|
||||||
|
{
|
||||||
|
InstanceEditable = !(Desc->PropertyFlags & CPF_DisableEditOnInstance);
|
||||||
|
BlueprintReadOnly = (Desc->PropertyFlags & CPF_BlueprintReadOnly) != 0;
|
||||||
|
ExposeToCinematics = (Desc->PropertyFlags & CPF_Interp) != 0;
|
||||||
|
ExposeOnSpawn = Desc->HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn);
|
||||||
|
Private = Desc->HasMetaData(FBlueprintMetadata::MD_Private);
|
||||||
|
|
||||||
|
FString Tooltip;
|
||||||
|
if (Desc->HasMetaData(TEXT("tooltip")))
|
||||||
|
Description = Desc->GetMetaData(TEXT("tooltip"));
|
||||||
|
else
|
||||||
|
Description.Empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FBPVarEditor::Save() const
|
||||||
|
{
|
||||||
|
// CPF flags
|
||||||
|
if (InstanceEditable)
|
||||||
|
Desc->PropertyFlags &= ~CPF_DisableEditOnInstance;
|
||||||
|
else
|
||||||
|
Desc->PropertyFlags |= CPF_DisableEditOnInstance;
|
||||||
|
|
||||||
|
if (BlueprintReadOnly)
|
||||||
|
Desc->PropertyFlags |= CPF_BlueprintReadOnly;
|
||||||
|
else
|
||||||
|
Desc->PropertyFlags &= ~CPF_BlueprintReadOnly;
|
||||||
|
|
||||||
|
if (ExposeToCinematics)
|
||||||
|
Desc->PropertyFlags |= CPF_Interp;
|
||||||
|
else
|
||||||
|
Desc->PropertyFlags &= ~CPF_Interp;
|
||||||
|
|
||||||
|
// Metadata flags
|
||||||
|
if (ExposeOnSpawn)
|
||||||
|
Desc->SetMetaData(FBlueprintMetadata::MD_ExposeOnSpawn, TEXT("true"));
|
||||||
|
else
|
||||||
|
Desc->RemoveMetaData(FBlueprintMetadata::MD_ExposeOnSpawn);
|
||||||
|
|
||||||
|
if (Private)
|
||||||
|
Desc->SetMetaData(FBlueprintMetadata::MD_Private, TEXT("true"));
|
||||||
|
else
|
||||||
|
Desc->RemoveMetaData(FBlueprintMetadata::MD_Private);
|
||||||
|
|
||||||
|
// Description/tooltip
|
||||||
|
if (!Description.IsEmpty())
|
||||||
|
Desc->SetMetaData(TEXT("tooltip"), Description);
|
||||||
|
else
|
||||||
|
Desc->RemoveMetaData(TEXT("tooltip"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TArray<MCPProperty> FBPVarEditor::MergedProperties()
|
||||||
|
{
|
||||||
|
TArray<MCPProperty> Props = MCPProperty::GetAll(
|
||||||
|
FBPVariableDescription::StaticStruct(), Desc, CPF_Edit);
|
||||||
|
|
||||||
|
MCPProperty::Remove(Props, TEXT("PropertyFlags"));
|
||||||
|
MCPProperty::Remove(Props, TEXT("MetaDataArray"));
|
||||||
|
MCPProperty::Remove(Props, TEXT("VarName"));
|
||||||
|
MCPProperty::Remove(Props, TEXT("VarGuid"));
|
||||||
|
MCPProperty::Remove(Props, TEXT("DefaultValue"));
|
||||||
|
|
||||||
|
Props.Append(MCPProperty::GetAll(
|
||||||
|
FBPVarEditor::StaticStruct(), this, (EPropertyFlags)0));
|
||||||
|
|
||||||
|
return Props;
|
||||||
|
}
|
||||||
@@ -6,46 +6,10 @@
|
|||||||
#include "Dom/JsonValue.h"
|
#include "Dom/JsonValue.h"
|
||||||
|
|
||||||
|
|
||||||
|
bool MCPJson::PopulateFromJson(MCPProperty& P, const FJsonObject* Json, bool AllOptional)
|
||||||
bool MCPJson::PopulateFromJson(
|
|
||||||
UStruct* StructType, void* Container, const FJsonObject* Json)
|
|
||||||
{
|
{
|
||||||
bool Ok = true;
|
FString JsonKey = P.Prop->GetName();
|
||||||
|
bool bOptional = AllOptional || P.Prop->HasMetaData(TEXT("Optional"));
|
||||||
// Build a set of known property names (as JSON keys) for the unknown-field check.
|
|
||||||
TSet<FString> KnownKeys;
|
|
||||||
TArray<FProperty*> Properties;
|
|
||||||
|
|
||||||
for (TFieldIterator<FProperty> It(StructType, EFieldIterationFlags::None); It; ++It)
|
|
||||||
{
|
|
||||||
FProperty* Prop = *It;
|
|
||||||
Properties.Add(Prop);
|
|
||||||
KnownKeys.Add(Prop->GetName());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for unknown fields in the JSON
|
|
||||||
for (const auto& KV : Json->Values)
|
|
||||||
{
|
|
||||||
if (!KnownKeys.Contains(KV.Key))
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
|
|
||||||
Ok = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Populate each property from JSON
|
|
||||||
for (FProperty* Property : Properties)
|
|
||||||
{
|
|
||||||
if (!PopulateFromJson(Property, Container, Json)) Ok = false;
|
|
||||||
}
|
|
||||||
return Ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool MCPJson::PopulateFromJson(
|
|
||||||
FProperty* Property, void* Container, const FJsonObject* Json)
|
|
||||||
{
|
|
||||||
FString JsonKey = Property->GetName();
|
|
||||||
bool bOptional = Property->HasMetaData(TEXT("Optional"));
|
|
||||||
|
|
||||||
if (!Json->HasField(JsonKey))
|
if (!Json->HasField(JsonKey))
|
||||||
{
|
{
|
||||||
@@ -57,10 +21,10 @@ bool MCPJson::PopulateFromJson(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void* ValuePtr = Property->ContainerPtrToValuePtr<void>(Container);
|
void* ValuePtr = P.Prop->ContainerPtrToValuePtr<void>(P.Container);
|
||||||
|
|
||||||
// Special handling for FMCPJsonObject and FMCPJsonArray
|
// Special handling for FMCPJsonObject and FMCPJsonArray
|
||||||
if (FStructProperty* StructProp = CastField<FStructProperty>(Property))
|
if (FStructProperty* StructProp = CastField<FStructProperty>(P.Prop))
|
||||||
{
|
{
|
||||||
if (StructProp->Struct == FMCPJsonObject::StaticStruct())
|
if (StructProp->Struct == FMCPJsonObject::StaticStruct())
|
||||||
{
|
{
|
||||||
@@ -91,42 +55,70 @@ bool MCPJson::PopulateFromJson(
|
|||||||
if (JsonValue->Type == EJson::Number)
|
if (JsonValue->Type == EJson::Number)
|
||||||
{
|
{
|
||||||
double D = JsonValue->AsNumber();
|
double D = JsonValue->AsNumber();
|
||||||
if (FIntProperty* IntProp = CastField<FIntProperty>(Property))
|
if (FIntProperty* IntProp = CastField<FIntProperty>(P.Prop))
|
||||||
{ IntProp->SetPropertyValue(ValuePtr, (int32)D); return true; }
|
{ IntProp->SetPropertyValue(ValuePtr, (int32)D); return true; }
|
||||||
if (FFloatProperty* FloatProp = CastField<FFloatProperty>(Property))
|
if (FFloatProperty* FloatProp = CastField<FFloatProperty>(P.Prop))
|
||||||
{ FloatProp->SetPropertyValue(ValuePtr, (float)D); return true; }
|
{ FloatProp->SetPropertyValue(ValuePtr, (float)D); return true; }
|
||||||
if (FDoubleProperty* DoubleProp = CastField<FDoubleProperty>(Property))
|
if (FDoubleProperty* DoubleProp = CastField<FDoubleProperty>(P.Prop))
|
||||||
{ DoubleProp->SetPropertyValue(ValuePtr, D); return true; }
|
{ DoubleProp->SetPropertyValue(ValuePtr, D); return true; }
|
||||||
if (FByteProperty* ByteProp = CastField<FByteProperty>(Property))
|
if (FByteProperty* ByteProp = CastField<FByteProperty>(P.Prop))
|
||||||
{ ByteProp->SetPropertyValue(ValuePtr, (uint8)D); return true; }
|
{ ByteProp->SetPropertyValue(ValuePtr, (uint8)D); return true; }
|
||||||
UMCPServer::Printf(TEXT("ERROR: '%s' received a number but expects %s\n"), *JsonKey, *Property->GetCPPType());
|
UMCPServer::Printf(TEXT("ERROR: '%s' received a number but expects %s\n"), *JsonKey, *P.Prop->GetCPPType());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JsonValue->Type == EJson::Boolean)
|
if (JsonValue->Type == EJson::Boolean)
|
||||||
{
|
{
|
||||||
if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Property))
|
if (FBoolProperty* BoolProp = CastField<FBoolProperty>(P.Prop))
|
||||||
{ BoolProp->SetPropertyValue(ValuePtr, JsonValue->AsBool()); return true; }
|
{ BoolProp->SetPropertyValue(ValuePtr, JsonValue->AsBool()); return true; }
|
||||||
UMCPServer::Printf(TEXT("ERROR: '%s' received a boolean but expects %s\n"), *JsonKey, *Property->GetCPPType());
|
UMCPServer::Printf(TEXT("ERROR: '%s' received a boolean but expects %s\n"), *JsonKey, *P.Prop->GetCPPType());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (JsonValue->Type == EJson::String)
|
if (JsonValue->Type == EJson::String)
|
||||||
{
|
{
|
||||||
FString ValueStr = JsonValue->AsString();
|
return P.SetText(JsonValue->AsString());
|
||||||
const TCHAR* Result = Property->ImportText_Direct(*ValueStr, ValuePtr, nullptr, PPF_None);
|
|
||||||
if (!Result)
|
|
||||||
{
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: Could not parse '%s' for parameter '%s'\n"), *ValueStr, *JsonKey);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UMCPServer::Printf(TEXT("ERROR: '%s' must be a string, number, or boolean\n"), *JsonKey);
|
UMCPServer::Printf(TEXT("ERROR: '%s' must be a string, number, or boolean\n"), *JsonKey);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MCPJson::PopulateFromJson(
|
||||||
|
TArray<MCPProperty>& Props, const FJsonObject* Json, bool AllOptional)
|
||||||
|
{
|
||||||
|
bool Ok = true;
|
||||||
|
|
||||||
|
// Build a set of known property names for the unknown-field check.
|
||||||
|
TSet<FString> KnownKeys;
|
||||||
|
for (const MCPProperty& P : Props)
|
||||||
|
KnownKeys.Add(P.Prop->GetName());
|
||||||
|
|
||||||
|
// Check for unknown fields in the JSON
|
||||||
|
for (const auto& KV : Json->Values)
|
||||||
|
{
|
||||||
|
if (!KnownKeys.Contains(KV.Key))
|
||||||
|
{
|
||||||
|
UMCPServer::Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
|
||||||
|
Ok = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate each property from JSON
|
||||||
|
for (MCPProperty& P : Props)
|
||||||
|
{
|
||||||
|
if (!PopulateFromJson(P, Json, AllOptional)) Ok = false;
|
||||||
|
}
|
||||||
|
return Ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MCPJson::PopulateFromJson(
|
||||||
|
UStruct* StructType, void* Container, const FJsonObject* Json)
|
||||||
|
{
|
||||||
|
TArray<MCPProperty> Props = MCPProperty::GetAll(StructType, Container, (EPropertyFlags)0);
|
||||||
|
return PopulateFromJson(Props, Json);
|
||||||
|
}
|
||||||
|
|
||||||
bool MCPJson::PopulateFromJson(
|
bool MCPJson::PopulateFromJson(
|
||||||
UStruct* StructType, void* Container,
|
UStruct* StructType, void* Container,
|
||||||
const TSharedPtr<FJsonValue>& JsonValue)
|
const TSharedPtr<FJsonValue>& JsonValue)
|
||||||
|
|||||||
@@ -1,16 +1,27 @@
|
|||||||
#include "MCPProperty.h"
|
#include "MCPProperty.h"
|
||||||
#include "MCPUtils.h"
|
#include "MCPUtils.h"
|
||||||
#include "MCPServer.h"
|
#include "MCPServer.h"
|
||||||
|
#include "MCPTypes.h"
|
||||||
|
#include "Engine/Blueprint.h"
|
||||||
#include "Materials/MaterialExpression.h"
|
#include "Materials/MaterialExpression.h"
|
||||||
#include "MaterialGraph/MaterialGraphNode.h"
|
#include "MaterialGraph/MaterialGraphNode.h"
|
||||||
|
#include "EdGraph/EdGraphPin.h"
|
||||||
|
|
||||||
|
static bool IsPinTypeProperty(FProperty* Prop)
|
||||||
|
{
|
||||||
|
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
|
||||||
|
return StructProp && StructProp->Struct == FEdGraphPinType::StaticStruct();
|
||||||
|
}
|
||||||
|
|
||||||
MCPProperty::MCPProperty(FProperty* InProp, void* InContainer)
|
MCPProperty::MCPProperty(FProperty* InProp, void* InContainer)
|
||||||
: Prop(InProp), Container(InContainer) {}
|
: Prop(InProp), Container(InContainer) {}
|
||||||
|
|
||||||
FString MCPProperty::GetText() const
|
FString MCPProperty::GetText() const
|
||||||
{
|
{
|
||||||
FString Result;
|
|
||||||
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||||
|
if (IsPinTypeProperty(Prop))
|
||||||
|
return UMCPTypes::TypeToText(*static_cast<FEdGraphPinType*>(ValuePtr));
|
||||||
|
FString Result;
|
||||||
Prop->ExportTextItem_Direct(Result, ValuePtr, nullptr, nullptr, PPF_None);
|
Prop->ExportTextItem_Direct(Result, ValuePtr, nullptr, nullptr, PPF_None);
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
@@ -18,6 +29,8 @@ FString MCPProperty::GetText() const
|
|||||||
bool MCPProperty::SetText(const FString& Value)
|
bool MCPProperty::SetText(const FString& Value)
|
||||||
{
|
{
|
||||||
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||||
|
if (IsPinTypeProperty(Prop))
|
||||||
|
return UMCPTypes::TextToType(Value, *static_cast<FEdGraphPinType*>(ValuePtr));
|
||||||
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, nullptr, PPF_None);
|
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, nullptr, PPF_None);
|
||||||
if (!ImportResult)
|
if (!ImportResult)
|
||||||
{
|
{
|
||||||
@@ -35,15 +48,20 @@ bool MCPProperty::SetText(const FString& Value)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MCPProperty::Collect(UObject *Obj, TArray<MCPProperty> &Props, EPropertyFlags Flags)
|
void MCPProperty::Collect(UStruct* StructType, void* Container, TArray<MCPProperty> &Props, EPropertyFlags Flags)
|
||||||
{
|
{
|
||||||
for (TFieldIterator<FProperty> It(Obj->GetClass()); It; ++It)
|
for (TFieldIterator<FProperty> It(StructType); It; ++It)
|
||||||
{
|
{
|
||||||
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
|
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
|
||||||
Props.Emplace(*It, Obj);
|
Props.Emplace(*It, Container);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MCPProperty::Remove(TArray<MCPProperty>& Props, const FString& Name)
|
||||||
|
{
|
||||||
|
Props.RemoveAll([&](const MCPProperty& P) { return P.Prop->GetName() == Name; });
|
||||||
|
}
|
||||||
|
|
||||||
TArray<MCPProperty> MCPProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
|
TArray<MCPProperty> MCPProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
|
||||||
{
|
{
|
||||||
if (!Obj) return {};
|
if (!Obj) return {};
|
||||||
@@ -63,7 +81,7 @@ TArray<MCPProperty> MCPProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
|
|||||||
Obj = BP->GeneratedClass->GetDefaultObject();
|
Obj = BP->GeneratedClass->GetDefaultObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
Collect(Obj, Result, Flags);
|
Collect(Obj->GetClass(), Obj, Result, Flags);
|
||||||
|
|
||||||
// If it's a Material Graph node, also collect properties from
|
// If it's a Material Graph node, also collect properties from
|
||||||
// the associated material expression.
|
// the associated material expression.
|
||||||
@@ -72,12 +90,19 @@ TArray<MCPProperty> MCPProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
|
|||||||
{
|
{
|
||||||
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
|
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
|
||||||
{
|
{
|
||||||
Collect(Expr, Result, Flags);
|
Collect(Expr->GetClass(), Expr, Result, Flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TArray<MCPProperty> MCPProperty::GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags)
|
||||||
|
{
|
||||||
|
TArray<MCPProperty> Result;
|
||||||
|
Collect(StructType, Container, Result, Flags);
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
TArray<MCPProperty> MCPProperty::GetAllSubstring(UObject* Obj, EPropertyFlags Flags, const FString& Substring)
|
TArray<MCPProperty> MCPProperty::GetAllSubstring(UObject* Obj, EPropertyFlags Flags, const FString& Substring)
|
||||||
{
|
{
|
||||||
TArray<MCPProperty> All = GetAll(Obj, Flags);
|
TArray<MCPProperty> All = GetAll(Obj, Flags);
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "CoreMinimal.h"
|
||||||
|
#include "MCPProperty.h"
|
||||||
|
#include "Engine/Blueprint.h"
|
||||||
|
#include "BPVarEditor.generated.h"
|
||||||
|
|
||||||
|
// Editor-friendly view of a blueprint variable's properties.
|
||||||
|
// Wraps an FBPVariableDescription, exposing commonly-used flags
|
||||||
|
// and metadata as simple UPROPERTYs that the property system can
|
||||||
|
// populate from JSON.
|
||||||
|
USTRUCT()
|
||||||
|
struct FBPVarEditor
|
||||||
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
|
|
||||||
|
FBPVariableDescription* Desc = nullptr;
|
||||||
|
|
||||||
|
FBPVarEditor() = default;
|
||||||
|
FBPVarEditor(FBPVariableDescription* InDesc) : Desc(InDesc) {}
|
||||||
|
FBPVarEditor(UBlueprint* BP, const FString& VarName);
|
||||||
|
|
||||||
|
bool NotFound() const { return Desc == nullptr; }
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, meta=(Optional, Description="Variable description/tooltip"))
|
||||||
|
FString Description;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, meta=(Optional, Description="Allow editing on instances"))
|
||||||
|
bool InstanceEditable = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, meta=(Optional, Description="Read-only in blueprints"))
|
||||||
|
bool BlueprintReadOnly = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, meta=(Optional, Description="Expose as a pin when spawning"))
|
||||||
|
bool ExposeOnSpawn = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, meta=(Optional, Description="Private to this blueprint"))
|
||||||
|
bool Private = false;
|
||||||
|
|
||||||
|
UPROPERTY(EditAnywhere, meta=(Optional, Description="Expose to cinematics/sequencer"))
|
||||||
|
bool ExposeToCinematics = false;
|
||||||
|
|
||||||
|
// Load from Desc, populate from JSON, save back to Desc.
|
||||||
|
bool LoadJson(const FJsonObject* Json);
|
||||||
|
|
||||||
|
// Print all properties and their current values.
|
||||||
|
void Dump();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void Load();
|
||||||
|
void Save() const;
|
||||||
|
TArray<MCPProperty> MergedProperties();
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
#include "MCPHandler.h"
|
#include "MCPHandler.h"
|
||||||
|
#include "MCPProperty.h"
|
||||||
#include "Dom/JsonObject.h"
|
#include "Dom/JsonObject.h"
|
||||||
|
|
||||||
// JSON utility functions used by MCP handlers.
|
// JSON utility functions used by MCP handlers.
|
||||||
@@ -9,7 +10,8 @@
|
|||||||
class MCPJson
|
class MCPJson
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static bool PopulateFromJson(FProperty* Property, void* Container, const FJsonObject* Json);
|
static bool PopulateFromJson(MCPProperty& Prop, const FJsonObject* Json, bool AllOptional = false);
|
||||||
|
static bool PopulateFromJson(TArray<MCPProperty>& Props, const FJsonObject* Json, bool AllOptional = false);
|
||||||
static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr<FJsonValue>& JsonValue);
|
static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr<FJsonValue>& JsonValue);
|
||||||
static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Json);
|
static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Json);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -20,11 +20,13 @@ public:
|
|||||||
explicit operator bool() const { return Prop != nullptr; }
|
explicit operator bool() const { return Prop != nullptr; }
|
||||||
FProperty* operator->() const { return Prop; }
|
FProperty* operator->() const { return Prop; }
|
||||||
|
|
||||||
|
static void Remove(TArray<MCPProperty>& Props, const FString& Name);
|
||||||
static TArray<MCPProperty> GetAll(UObject* Obj, EPropertyFlags Flags);
|
static TArray<MCPProperty> GetAll(UObject* Obj, EPropertyFlags Flags);
|
||||||
|
static TArray<MCPProperty> GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags);
|
||||||
static TArray<MCPProperty> GetAllSubstring(UObject* Obj, EPropertyFlags Flags, const FString& Substring);
|
static TArray<MCPProperty> GetAllSubstring(UObject* Obj, EPropertyFlags Flags, const FString& Substring);
|
||||||
static TArray<MCPProperty> GetAllExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name);
|
static TArray<MCPProperty> GetAllExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name);
|
||||||
static MCPProperty GetOneExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name);
|
static MCPProperty GetOneExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void Collect(UObject *Obj, TArray<MCPProperty> &Props, EPropertyFlags Flags);
|
static void Collect(UStruct* StructType, void* Container, TArray<MCPProperty> &Props, EPropertyFlags Flags);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user