|
|
|
|
@@ -1,4 +1,5 @@
|
|
|
|
|
#include "MCPUtils.h"
|
|
|
|
|
#include "MCPServer.h"
|
|
|
|
|
#include "MCPHandler.h"
|
|
|
|
|
#include "Dom/JsonValue.h"
|
|
|
|
|
#include "Serialization/JsonReader.h"
|
|
|
|
|
@@ -90,22 +91,6 @@ extern int32 TrySavePackageSEH(
|
|
|
|
|
FSavePackageArgs* SaveArgs, ESavePackageResult* OutResult);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// MCPErrorCallback
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
MCPErrorCallback::MCPErrorCallback(std::nullptr_t)
|
|
|
|
|
: Func([](const FString&) {})
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
MCPErrorCallback::MCPErrorCallback(FString& OutError)
|
|
|
|
|
: Func([&OutError](const FString& Msg) { OutError = Msg; })
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
MCPErrorCallback::MCPErrorCallback(FStringBuilderBase& OutResult)
|
|
|
|
|
: Func([&OutResult](const FString& Msg) { OutResult.Appendf(TEXT("ERROR: %s\n"), *Msg); })
|
|
|
|
|
{}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// Name Formatting
|
|
|
|
|
// ============================================================
|
|
|
|
|
@@ -343,12 +328,12 @@ FString MCPUtils::EnumToString(UEnum* Enum, int64 Value, const FString& Prefix)
|
|
|
|
|
return Full;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MCPUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue, MCPErrorCallback Error, const FString& Prefix)
|
|
|
|
|
bool MCPUtils::StringToEnum(UEnum* Enum, const FString& Str, int64& OutValue, const FString& Prefix)
|
|
|
|
|
{
|
|
|
|
|
OutValue = Enum->GetValueByNameString(Prefix + Str);
|
|
|
|
|
if (OutValue == INDEX_NONE)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Invalid value '%s' for %s"), *Str, *Enum->GetName()));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Invalid value '%s' for %s\n"), *Str, *Enum->GetName());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
@@ -541,7 +526,7 @@ UClass* MCPUtils::FindClassByName(const FString& ClassName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MCPUtils::ResolveTypeFromString(
|
|
|
|
|
const FString& TypeName, FEdGraphPinType& OutPinType, MCPErrorCallback Error)
|
|
|
|
|
const FString& TypeName, FEdGraphPinType& OutPinType)
|
|
|
|
|
{
|
|
|
|
|
FString TypeLower = TypeName.ToLower();
|
|
|
|
|
|
|
|
|
|
@@ -614,7 +599,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|
|
|
|
UClass* FoundClass = FindClassByName(ClassName);
|
|
|
|
|
if (!FoundClass)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Class '%s' not found for object reference type"), *ClassName));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for object reference type\n"), *ClassName);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Object;
|
|
|
|
|
@@ -626,7 +611,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|
|
|
|
UClass* FoundClass = FindClassByName(ClassName);
|
|
|
|
|
if (!FoundClass)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Class '%s' not found for soft object reference type"), *ClassName));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for soft object reference type\n"), *ClassName);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftObject;
|
|
|
|
|
@@ -638,7 +623,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|
|
|
|
UClass* FoundClass = FindClassByName(ClassName);
|
|
|
|
|
if (!FoundClass)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Class '%s' not found for class reference type (TSubclassOf)"), *ClassName));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for class reference type (TSubclassOf)\n"), *ClassName);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Class;
|
|
|
|
|
@@ -650,7 +635,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|
|
|
|
UClass* FoundClass = FindClassByName(ClassName);
|
|
|
|
|
if (!FoundClass)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Class '%s' not found for soft class reference type"), *ClassName));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for soft class reference type\n"), *ClassName);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_SoftClass;
|
|
|
|
|
@@ -662,7 +647,7 @@ bool MCPUtils::ResolveTypeFromString(
|
|
|
|
|
UClass* FoundClass = FindClassByName(ClassName);
|
|
|
|
|
if (!FoundClass)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Class '%s' not found for interface reference type"), *ClassName));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Class '%s' not found for interface reference type\n"), *ClassName);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
OutPinType.PinCategory = UEdGraphSchema_K2::PC_Interface;
|
|
|
|
|
@@ -738,9 +723,9 @@ bool MCPUtils::ResolveTypeFromString(
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(
|
|
|
|
|
TEXT("Unknown type '%s'. Use: bool, int, float, string, name, text, byte, vector, rotator, transform, object, a struct/enum name (e.g. FVector, EMyEnum), or colon syntax for references (object:Actor, softobject:Actor, class:Actor, softclass:Actor, interface:MyInterface)"),
|
|
|
|
|
*TypeName));
|
|
|
|
|
UMCPServer::Printf(
|
|
|
|
|
TEXT("ERROR: Unknown type '%s'. Use: bool, int, float, string, name, text, byte, vector, rotator, transform, object, a struct/enum name (e.g. FVector, EMyEnum), or colon syntax for references (object:Actor, softobject:Actor, class:Actor, softclass:Actor, interface:MyInterface)\n"),
|
|
|
|
|
*TypeName);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -798,54 +783,6 @@ UMaterial* MCPUtils::ReplaceMaterialWithTransientCopy(UMaterial* Material)
|
|
|
|
|
return Material;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ============================================================
|
|
|
|
|
// PreEdit / PostEdit
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
void MCPUtils::PreEdit(const TArray<UObject*>& Objects)
|
|
|
|
|
{
|
|
|
|
|
for (UObject* Obj : Objects)
|
|
|
|
|
Obj->PreEditChange(nullptr);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
|
|
|
|
|
{
|
|
|
|
|
TSet<UEdGraphNode*> Nodes;
|
|
|
|
|
TSet<UEdGraph*> Graphs;
|
|
|
|
|
TSet<UMaterial*> Materials;
|
|
|
|
|
TSet<UBlueprint*> Blueprints;
|
|
|
|
|
for (int32 i = Objects.Num() - 1; i >= 0; --i)
|
|
|
|
|
{
|
|
|
|
|
UObject* Obj = Objects[i];
|
|
|
|
|
Obj->PostEditChange();
|
|
|
|
|
Obj->MarkPackageDirty();
|
|
|
|
|
|
|
|
|
|
if (UEdGraphNode* Node = Cast<UEdGraphNode>(Obj))
|
|
|
|
|
Nodes.Add(Node);
|
|
|
|
|
|
|
|
|
|
if (UEdGraph* Graph = Cast<UEdGraph>(Obj))
|
|
|
|
|
Graphs.Add(Graph);
|
|
|
|
|
|
|
|
|
|
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
|
|
|
|
|
Blueprints.Add(BP);
|
|
|
|
|
|
|
|
|
|
if (UMaterialInterface* MatIface = Cast<UMaterialInterface>(Obj))
|
|
|
|
|
if (UMaterial* BaseMat = MatIface->GetMaterial())
|
|
|
|
|
Materials.Add(BaseMat);
|
|
|
|
|
}
|
|
|
|
|
for (UEdGraphNode* Node : Nodes)
|
|
|
|
|
Node->ReconstructNode();
|
|
|
|
|
for (UEdGraph* Graph : Graphs)
|
|
|
|
|
Graph->NotifyGraphChanged();
|
|
|
|
|
for (UMaterial *Material : Materials)
|
|
|
|
|
UMaterialEditingLibrary::RebuildMaterialInstanceEditors(Material);
|
|
|
|
|
for (UBlueprint *Blueprint : Blueprints)
|
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
|
|
|
|
|
|
|
|
|
|
if (GEditor)
|
|
|
|
|
GEditor->RedrawAllViewports();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> MCPUtils::GetMaterialParameters(UMaterialInterface* Material)
|
|
|
|
|
{
|
|
|
|
|
@@ -860,7 +797,7 @@ TMap<FMaterialParameterInfo, FMaterialParameterMetadata> MCPUtils::GetMaterialPa
|
|
|
|
|
return Result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MCPUtils::ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation, MCPErrorCallback Error)
|
|
|
|
|
bool MCPUtils::ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation)
|
|
|
|
|
{
|
|
|
|
|
if (Str.Equals(TEXT("Global"), ESearchCase::IgnoreCase))
|
|
|
|
|
OutAssociation = GlobalParameter;
|
|
|
|
|
@@ -870,7 +807,7 @@ bool MCPUtils::ParseMaterialParameterAssociation(const FString& Str, EMaterialPa
|
|
|
|
|
OutAssociation = BlendParameter;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Invalid ParameterAssociation '%s' (expected 'Global', 'Layer', or 'Blend')"), *Str));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Invalid ParameterAssociation '%s' (expected 'Global', 'Layer', or 'Blend')\n"), *Str);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
@@ -984,7 +921,7 @@ UAnimationStateMachineGraph* MCPUtils::FindStateMachineGraph(UBlueprint* BP, con
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UAnimStateNode* MCPUtils::FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName, MCPErrorCallback Error)
|
|
|
|
|
UAnimStateNode* MCPUtils::FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName)
|
|
|
|
|
{
|
|
|
|
|
for (UEdGraphNode* Node : SMGraph->Nodes)
|
|
|
|
|
{
|
|
|
|
|
@@ -996,7 +933,7 @@ UAnimStateNode* MCPUtils::FindStateByName(UAnimationStateMachineGraph* SMGraph,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("State '%s' not found in graph '%s'"), *StateName, *SMGraph->GetName()));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: State '%s' not found in graph '%s'\n"), *StateName, *SMGraph->GetName());
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1229,22 +1166,20 @@ FString MCPUtils::SetPropertyFromJson(
|
|
|
|
|
bool MCPUtils::PopulateFromJson(
|
|
|
|
|
UStruct* StructType,
|
|
|
|
|
void* Container,
|
|
|
|
|
const TSharedPtr<FJsonValue>& JsonValue,
|
|
|
|
|
MCPErrorCallback Error)
|
|
|
|
|
const TSharedPtr<FJsonValue>& JsonValue)
|
|
|
|
|
{
|
|
|
|
|
if (!JsonValue.IsValid() || (JsonValue->Type != EJson::Object))
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(TEXT("Expected a JSON object"));
|
|
|
|
|
UMCPServer::Print(TEXT("ERROR: Expected a JSON object\n"));
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return PopulateFromJson(StructType, Container, JsonValue->AsObject().Get(), Error);
|
|
|
|
|
return PopulateFromJson(StructType, Container, JsonValue->AsObject().Get());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MCPUtils::PopulateFromJson(
|
|
|
|
|
UStruct* StructType,
|
|
|
|
|
void* Container,
|
|
|
|
|
const FJsonObject* Json,
|
|
|
|
|
MCPErrorCallback Error)
|
|
|
|
|
const FJsonObject* Json)
|
|
|
|
|
{
|
|
|
|
|
// Build a set of known property names (as JSON keys) for the unknown-field check.
|
|
|
|
|
TSet<FString> KnownKeys;
|
|
|
|
|
@@ -1262,7 +1197,7 @@ bool MCPUtils::PopulateFromJson(
|
|
|
|
|
{
|
|
|
|
|
if (!KnownKeys.Contains(KV.Key))
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Unknown parameter '%s'"), *KV.Key));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1277,7 +1212,7 @@ bool MCPUtils::PopulateFromJson(
|
|
|
|
|
{
|
|
|
|
|
if (!bOptional)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Missing required parameter '%s'"), *JsonKey));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Missing required parameter '%s'\n"), *JsonKey);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
@@ -1286,7 +1221,7 @@ bool MCPUtils::PopulateFromJson(
|
|
|
|
|
FString PropError = SetPropertyFromJson(Container, Prop, JsonKey, Json);
|
|
|
|
|
if (!PropError.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(PropError);
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: %s\n"), *PropError);
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -1369,11 +1304,11 @@ FString MCPUtils::FormatPropertyType(FProperty* Prop)
|
|
|
|
|
// FindPropertyByName
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
FProperty* MCPUtils::FindPropertyByName(UObject* Obj, const FString& Name, MCPErrorCallback Error)
|
|
|
|
|
FProperty* MCPUtils::FindPropertyByName(UObject* Obj, const FString& Name)
|
|
|
|
|
{
|
|
|
|
|
if (!Obj)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(TEXT("Object is null"));
|
|
|
|
|
UMCPServer::Print(TEXT("ERROR: Object is null\n"));
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@@ -1383,14 +1318,14 @@ FProperty* MCPUtils::FindPropertyByName(UObject* Obj, const FString& Name, MCPEr
|
|
|
|
|
if (!Identifies(Name, *PropIt)) continue;
|
|
|
|
|
if (Found)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Ambiguous property '%s' on %s"), *Name, *FormatName(Obj->GetClass())));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Ambiguous property '%s' on %s\n"), *Name, *FormatName(Obj->GetClass()));
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
Found = *PropIt;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!Found)
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Property '%s' not found on %s"), *Name, *FormatName(Obj->GetClass())));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Property '%s' not found on %s\n"), *Name, *FormatName(Obj->GetClass()));
|
|
|
|
|
|
|
|
|
|
return Found;
|
|
|
|
|
}
|
|
|
|
|
@@ -1411,27 +1346,27 @@ FString MCPUtils::GetPropertyValueText(UObject* Container, FProperty* Prop)
|
|
|
|
|
// SetPropertyValueText
|
|
|
|
|
// ============================================================
|
|
|
|
|
|
|
|
|
|
bool MCPUtils::SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value, MCPErrorCallback Error)
|
|
|
|
|
bool MCPUtils::SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value)
|
|
|
|
|
{
|
|
|
|
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
|
|
|
|
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Container, PPF_None);
|
|
|
|
|
if (!ImportResult)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Failed to parse '%s' for property '%s' (type: %s)"),
|
|
|
|
|
*Value, *FormatName(Prop), *Prop->GetCPPType()));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
|
|
|
|
|
*Value, *FormatName(Prop), *Prop->GetCPPType());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool MCPUtils::SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner, MCPErrorCallback Error)
|
|
|
|
|
bool MCPUtils::SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner)
|
|
|
|
|
{
|
|
|
|
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
|
|
|
|
const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Owner, PPF_None);
|
|
|
|
|
if (!ImportResult)
|
|
|
|
|
{
|
|
|
|
|
Error.SetError(FString::Printf(TEXT("Failed to parse '%s' for property '%s' (type: %s)"),
|
|
|
|
|
*Value, *FormatName(Prop), *Prop->GetCPPType()));
|
|
|
|
|
UMCPServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
|
|
|
|
|
*Value, *FormatName(Prop), *Prop->GetCPPType());
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
|