More progress on MCP

This commit is contained in:
2026-03-13 05:34:19 -04:00
parent 303c3fb03a
commit c35cfcd70c
14 changed files with 309 additions and 78 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -3,11 +3,12 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPNotifier.h"
#include "MCPProperty.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
#include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2.h"
#include "MaterialGraph/MaterialGraphSchema.h" #include "MaterialGraph/MaterialGraphSchema.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "GraphNode_SetDefaults.generated.h" #include "GraphNode_SetDefaults.generated.h"
@@ -53,9 +54,9 @@ public:
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
void HandleK2Entry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj, const UEdGraphSchema_K2* K2Schema, void HandleK2Entry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj, const UEdGraphSchema_K2* K2Schema,
TSet<UEdGraphNode*>& ModifiedNodes, FStringBuilderBase& Result) MCPNotifier& N, FStringBuilderBase& Result)
{ {
MCPFetcher F(Result, GraphObj); MCPFetcher F(Result, N, GraphObj);
UEdGraphPin* Pin = F.Node(Entry.Node).Pin(Entry.Name).Cast<UEdGraphPin>(); UEdGraphPin* Pin = F.Node(Entry.Node).Pin(Entry.Name).Cast<UEdGraphPin>();
if (!Pin) return; if (!Pin) return;
@@ -79,9 +80,8 @@ public:
Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error); Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error);
return; return;
} }
N.PreEditAddObject(Node);
K2Schema->TrySetDefaultValue(*Pin, Entry.Value); K2Schema->TrySetDefaultValue(*Pin, Entry.Value);
ModifiedNodes.Add(Node);
} }
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
@@ -89,34 +89,28 @@ public:
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
void HandleMaterialEntry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj, void HandleMaterialEntry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj,
TSet<UEdGraphNode*>& ModifiedNodes, FStringBuilderBase& Result) MCPNotifier& N, FStringBuilderBase& Result)
{ {
MCPFetcher F(Result, GraphObj); MCPFetcher F(Result, N, GraphObj);
UMaterialGraphNode* MatNode = F.Node(Entry.Node).Cast<UMaterialGraphNode>(); UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>();
if (!MatNode) return; if (!Node) return;
if (!MatNode->MaterialExpression)
{ MCPProperty P = MCPProperty::GetOneExactMatch(Node, CPF_Edit, Entry.Name, Result);
Result.Appendf(TEXT("error: %s has no material expression\n"), *MCPUtils::FormatName(MatNode)); if (!P) return;
N.PreEditAddObject(Node);
if (!P.SetText(Entry.Value, Result))
return; return;
} }
UMaterialExpression* Expression = MatNode->MaterialExpression;
FProperty* Prop = MCPUtils::FindPropertyByName(Expression, Entry.Name, Result);
if (!Prop) return;
if (!MCPUtils::SetPropertyValueText(Expression, Prop, Entry.Value, Result))
return;
Expression->ForcePropertyValueChanged(Prop);
ModifiedNodes.Add(MatNode);
}
// ----------------------------------------------------------------------- // -----------------------------------------------------------------------
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
// Fetch the graph once. // Fetch the graph once.
MCPFetcher GraphFetcher(Result); MCPNotifier N;
MCPFetcher GraphFetcher(Result, N);
UEdGraph* GraphObj = GraphFetcher.Walk(Graph).Cast<UEdGraph>(); UEdGraph* GraphObj = GraphFetcher.Walk(Graph).Cast<UEdGraph>();
if (!GraphObj) return; if (!GraphObj) return;
@@ -130,9 +124,7 @@ public:
return; return;
} }
TSet<UEdGraphNode*> ModifiedNodes; N.PreEdit();
GraphFetcher.PreEdit();
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array) for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
{ {
FSetNodeDefaultEntry Entry; FSetNodeDefaultEntry Entry;
@@ -140,16 +132,12 @@ public:
continue; continue;
if (K2Schema) if (K2Schema)
HandleK2Entry(Entry, GraphObj, K2Schema, ModifiedNodes, Result); HandleK2Entry(Entry, GraphObj, K2Schema, N, Result);
else if (MGSchema) else if (MGSchema)
HandleMaterialEntry(Entry, GraphObj, ModifiedNodes, Result); HandleMaterialEntry(Entry, GraphObj, N, Result);
} }
for (UEdGraphNode* Node : ModifiedNodes) N.PostEdit();
{
Node->ReconstructNode();
}
GraphFetcher.PostEdit();
Result.Appendf(TEXT("Done.\n")); Result.Appendf(TEXT("Done.\n"));
} }

View File

@@ -3,6 +3,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPProperty.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Property_Dump.generated.h" #include "Property_Dump.generated.h"
@@ -42,14 +43,19 @@ public:
UObject* Template = F.Walk(Path).Template().Cast<UObject>(); UObject* Template = F.Walk(Path).Template().Cast<UObject>();
if (!Template) return; if (!Template) return;
TArray<FProperty*> Props = MCPUtils::SearchProperties(Template, Query, CPF_Edit, Local); TArray<MCPProperty> Props = MCPProperty::GetAllSubstring(Template, CPF_Edit, Query);
if (Local)
{
UClass* ObjClass = Template->GetClass();
Props.RemoveAll([ObjClass](const MCPProperty& P) { return P->GetOwnerStruct() != ObjClass; });
}
// Group properties by category. // Group properties by category.
TMap<FString, TArray<FProperty*>> ByCategory; TMap<FString, TArray<MCPProperty>> ByCategory;
for (FProperty* Prop : Props) for (MCPProperty& P : Props)
{ {
FString Category = Prop->HasMetaData(TEXT("Category")) ? Prop->GetMetaData(TEXT("Category")) : FString(); FString Category = P->HasMetaData(TEXT("Category")) ? P->GetMetaData(TEXT("Category")) : FString();
ByCategory.FindOrAdd(Category).Add(Prop); ByCategory.FindOrAdd(Category).Add(P);
} }
// Sort category names, putting empty category last. // Sort category names, putting empty category last.
@@ -68,18 +74,18 @@ public:
else else
Result.Appendf(TEXT("\n%s:\n"), *Category); Result.Appendf(TEXT("\n%s:\n"), *Category);
for (FProperty* Prop : ByCategory[Category]) for (MCPProperty& P : ByCategory[Category])
{ {
FString PropName = MCPUtils::FormatName(Prop); FString PropName = MCPUtils::FormatName(P.Prop);
FString ValueStr = MCPUtils::GetPropertyValueText(Template, Prop); FString ValueStr = P.GetText();
if (Truncate && (ValueStr.Len() > 80)) if (Truncate && (ValueStr.Len() > 80))
ValueStr = ValueStr.Left(80) + TEXT("..."); ValueStr = ValueStr.Left(80) + TEXT("...");
bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst); bool bEditable = !P->HasAnyPropertyFlags(CPF_EditConst);
Result.Appendf(TEXT(" %s %s %s = %s\n"), Result.Appendf(TEXT(" %s %s %s = %s\n"),
bEditable ? TEXT("editable") : TEXT("readonly"), bEditable ? TEXT("editable") : TEXT("readonly"),
*MCPUtils::FormatPropertyType(Prop), *MCPUtils::FormatPropertyType(P.Prop),
*PropName, *PropName,
*ValueStr); *ValueStr);
} }

View File

@@ -0,0 +1,44 @@
#pragma once
#include "CoreMinimal.h"
#include "MCPHandler.h"
#include "MCPFetcher.h"
#include "MCPProperty.h"
#include "MCPUtils.h"
#include "Property_Get.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UMCP_Property_Get : public UObject, public IMCPHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="MCPFetcher path to the object (e.g. /Game/Materials/M_Gold or /Game/Tangibles/TAN_Char,component:Mesh0)"))
FString Path;
UPROPERTY(meta=(Description="Property name"))
FString Property;
virtual FString GetDescription() const override
{
return TEXT("Get the value of a single property on an object resolved via MCPFetcher path.");
}
virtual void Handle(FStringBuilderBase& Result) override
{
MCPFetcher F(Result);
UObject* Template = F.Walk(Path).Template().Cast<UObject>();
if (!Template) return;
MCPProperty P = MCPProperty::GetOneExactMatch(Template, CPF_Edit, Property, Result);
if (!P) return;
Result.Append(P.GetText());
Result.Append(TEXT("\n"));
}
};

View File

@@ -3,6 +3,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPHandler.h" #include "MCPHandler.h"
#include "MCPFetcher.h" #include "MCPFetcher.h"
#include "MCPProperty.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Property_Set.generated.h" #include "Property_Set.generated.h"
@@ -42,17 +43,12 @@ public:
return; return;
} }
// Validation pass — check all properties before modifying anything. // Validation pass — resolve all properties and values before modifying anything.
TArray<TPair<MCPProperty, FString>> Resolved;
for (const auto& Pair : Properties.Json->Values) for (const auto& Pair : Properties.Json->Values)
{ {
FProperty* Prop = MCPUtils::FindPropertyByName(Template, Pair.Key, Result); MCPProperty P = MCPProperty::GetOneExactMatch(Template, CPF_Edit, Pair.Key, Result);
if (!Prop) return; if (!P) return;
if (!Prop->HasAnyPropertyFlags(CPF_Edit))
{
Result.Appendf(TEXT("Error: Property '%s' is not editable (no Edit flag)\n"), *Pair.Key);
return;
}
FString ValueStr; FString ValueStr;
if (!Pair.Value->TryGetString(ValueStr)) if (!Pair.Value->TryGetString(ValueStr))
@@ -60,25 +56,17 @@ public:
Result.Appendf(TEXT("Error: Value for '%s' must be a string\n"), *Pair.Key); Result.Appendf(TEXT("Error: Value for '%s' must be a string\n"), *Pair.Key);
return; return;
} }
Resolved.Emplace(P, ValueStr);
} }
// Apply all changes in a single Pre/PostEditChange bracket. // Apply all changes in a single Pre/PostEditChange bracket.
F.PreEdit(); F.PreEdit();
int32 SuccessCount = 0; int32 SuccessCount = 0;
for (const auto& Pair : Properties.Json->Values) for (auto& [P, ValueStr] : Resolved)
{ {
FProperty* Prop = MCPUtils::FindPropertyByName(Template, Pair.Key); if (!P.SetText(ValueStr, Result))
FString ValueStr;
Pair.Value->TryGetString(ValueStr);
FString OldValue = MCPUtils::GetPropertyValueText(Template, Prop);
if (!MCPUtils::SetPropertyValueText(Template, Prop, ValueStr, Result))
continue; continue;
FString NewValue = MCPUtils::GetPropertyValueText(Template, Prop);
Result.Appendf(TEXT("%s: %s -> %s\n"), *MCPUtils::FormatName(Prop), *OldValue, *NewValue);
SuccessCount++; SuccessCount++;
} }

View File

@@ -330,3 +330,4 @@ MCPFetcher& MCPFetcher::ToGraph()
return TypeMismatch(TEXT("ToGraph"), TEXT("Graph or Material")); return TypeMismatch(TEXT("ToGraph"), TEXT("Graph or Material"));
} }

View File

@@ -0,0 +1,65 @@
#include "MCPNotifier.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraph.h"
#include "Engine/Blueprint.h"
#include "Materials/Material.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "MaterialEditingLibrary.h"
void MCPNotifier::PreEditAddObject(UObject* Obj)
{
if (!Obj) return;
bool bAlreadyInSet = false;
TouchedSet.Add(Obj, &bAlreadyInSet);
if (bAlreadyInSet) return;
TouchedArray.Add(Obj);
if (bInsidePrePost)
Obj->PreEditChange(nullptr);
}
void MCPNotifier::PreEdit()
{
bInsidePrePost = true;
for (UObject* Obj : TouchedArray)
Obj->PreEditChange(nullptr);
}
void MCPNotifier::PostEdit()
{
TSet<UEdGraphNode*> Nodes;
TSet<UEdGraph*> Graphs;
TSet<UMaterial*> Materials;
TSet<UBlueprint*> Blueprints;
for (int32 i = TouchedArray.Num() - 1; i >= 0; --i)
{
UObject* Obj = TouchedArray[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();
bInsidePrePost = false;
}

View File

@@ -1,18 +1,22 @@
#include "MCPProperty.h" #include "MCPProperty.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "Materials/MaterialExpression.h"
#include "MaterialGraph/MaterialGraphNode.h"
MCPProperty::MCPProperty(FProperty* InProp, void* Container) MCPProperty::MCPProperty(FProperty* InProp, void* InContainer)
: Prop(InProp), ValuePtr(InProp ? InProp->ContainerPtrToValuePtr<void>(Container) : nullptr) {} : Prop(InProp), Container(InContainer) {}
FString MCPProperty::GetText() const FString MCPProperty::GetText() const
{ {
FString Result; FString Result;
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
Prop->ExportTextItem_Direct(Result, ValuePtr, nullptr, nullptr, PPF_None); Prop->ExportTextItem_Direct(Result, ValuePtr, nullptr, nullptr, PPF_None);
return Result; return Result;
} }
bool MCPProperty::SetText(const FString& Value, MCPErrorCallback Error) bool MCPProperty::SetText(const FString& Value, MCPErrorCallback Error)
{ {
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
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)
{ {
@@ -20,5 +24,78 @@ bool MCPProperty::SetText(const FString& Value, MCPErrorCallback Error)
*Value, *MCPUtils::FormatName(Prop), *Prop->GetCPPType())); *Value, *MCPUtils::FormatName(Prop), *Prop->GetCPPType()));
return false; return false;
} }
if (Prop->GetOwnerClass()->IsChildOf(UMaterialExpression::StaticClass()))
{
UMaterialExpression* Expr = static_cast<UMaterialExpression*>(Container);
Expr->ForcePropertyValueChanged(Prop);
}
return true; return true;
} }
TArray<MCPProperty> MCPProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
{
TArray<MCPProperty> Result;
if (!Obj) return Result;
for (TFieldIterator<FProperty> It(Obj->GetClass()); It; ++It)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Result.Emplace(*It, Obj);
}
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Obj))
{
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
{
for (TFieldIterator<FProperty> It(Expr->GetClass()); It; ++It)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Result.Emplace(*It, Expr);
}
}
}
return Result;
}
TArray<MCPProperty> MCPProperty::GetAllSubstring(UObject* Obj, EPropertyFlags Flags, const FString& Substring)
{
TArray<MCPProperty> All = GetAll(Obj, Flags);
if (Substring.IsEmpty()) return All;
TArray<MCPProperty> Result;
for (const MCPProperty& P : All)
{
if (MCPUtils::FormatName(P.Prop).Contains(Substring, ESearchCase::IgnoreCase))
Result.Add(P);
}
return Result;
}
TArray<MCPProperty> MCPProperty::GetAllExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name)
{
TArray<MCPProperty> All = GetAll(Obj, Flags);
TArray<MCPProperty> Result;
for (const MCPProperty& P : All)
{
if (MCPUtils::Identifies(Name, P.Prop))
Result.Add(P);
}
return Result;
}
MCPProperty MCPProperty::GetOneExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name, MCPErrorCallback Error)
{
TArray<MCPProperty> Matches = GetAllExactMatch(Obj, Flags, Name);
if (Matches.Num() == 0)
{
Error.SetError(FString::Printf(TEXT("Property '%s' not found on %s"),
*Name, *MCPUtils::FormatName(Obj->GetClass())));
return MCPProperty();
}
if (Matches.Num() > 1)
{
Error.SetError(FString::Printf(TEXT("Ambiguous property '%s' on %s"),
*Name, *MCPUtils::FormatName(Obj->GetClass())));
return MCPProperty();
}
return Matches[0];
}

View File

@@ -810,6 +810,7 @@ void MCPUtils::PreEdit(const TArray<UObject*>& Objects)
void MCPUtils::PostEdit(const TArray<UObject*>& Objects) void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
{ {
TSet<UEdGraphNode*> Nodes;
TSet<UEdGraph*> Graphs; TSet<UEdGraph*> Graphs;
TSet<UMaterial*> Materials; TSet<UMaterial*> Materials;
TSet<UBlueprint*> Blueprints; TSet<UBlueprint*> Blueprints;
@@ -819,6 +820,9 @@ void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
Obj->PostEditChange(); Obj->PostEditChange();
Obj->MarkPackageDirty(); Obj->MarkPackageDirty();
if (UEdGraphNode* Node = Cast<UEdGraphNode>(Obj))
Nodes.Add(Node);
if (UEdGraph* Graph = Cast<UEdGraph>(Obj)) if (UEdGraph* Graph = Cast<UEdGraph>(Obj))
Graphs.Add(Graph); Graphs.Add(Graph);
@@ -829,6 +833,8 @@ void MCPUtils::PostEdit(const TArray<UObject*>& Objects)
if (UMaterial* BaseMat = MatIface->GetMaterial()) if (UMaterial* BaseMat = MatIface->GetMaterial())
Materials.Add(BaseMat); Materials.Add(BaseMat);
} }
for (UEdGraphNode* Node : Nodes)
Node->ReconstructNode();
for (UEdGraph* Graph : Graphs) for (UEdGraph* Graph : Graphs)
Graph->NotifyGraphChanged(); Graph->NotifyGraphChanged();
for (UMaterial *Material : Materials) for (UMaterial *Material : Materials)

View File

@@ -2,6 +2,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "MCPUtils.h" #include "MCPUtils.h"
#include "MCPNotifier.h"
class UEdGraphPin; class UEdGraphPin;
class IAssetEditorInstance; class IAssetEditorInstance;
@@ -29,7 +30,9 @@ class MCPFetcher
{ {
public: public:
MCPFetcher(MCPErrorCallback CB) : ErrorCB(CB) {} MCPFetcher(MCPErrorCallback CB) : ErrorCB(CB) {}
MCPFetcher(MCPErrorCallback CB, UObject* O) : Obj(O), ErrorCB(CB) {} MCPFetcher(MCPErrorCallback CB, UObject* O) : ErrorCB(CB), Obj(O) {}
MCPFetcher(MCPErrorCallback CB, MCPNotifier& N) : ErrorCB(CB), Notifier(&N) {}
MCPFetcher(MCPErrorCallback CB, MCPNotifier& N, UObject* O) : ErrorCB(CB), Obj(O), Notifier(&N) {}
// Starting point is always Asset. // Starting point is always Asset.
MCPFetcher& Asset(const FString& PackagePath); MCPFetcher& Asset(const FString& PackagePath);
@@ -77,9 +80,7 @@ public:
if (!CheckAssetIsA(AssetType::StaticClass())) return nullptr; if (!CheckAssetIsA(AssetType::StaticClass())) return nullptr;
return static_cast<EditorType*>(Editor); return static_cast<EditorType*>(Editor);
} }
const TArray<UObject*>& Visited() const { return Chain; }
void PreEdit() { MCPUtils::PreEdit(Chain); }
void PostEdit() { MCPUtils::PostEdit(Chain); }
template<class T> T *Cast() template<class T> T *Cast()
{ {
if (bError) return nullptr; if (bError) return nullptr;
@@ -89,17 +90,33 @@ public:
return Result; return Result;
} }
void PreEditAddObject(UObject* Obj) { Notifier->PreEditAddObject(Obj); }
void PreEdit() { Notifier->PreEdit(); }
void PostEdit() { Notifier->PostEdit(); }
private: private:
bool bError = false; // The error callback is invoked whenever an error is detected.
MCPErrorCallback ErrorCB = nullptr;
// The Current Object or Pin
UObject* Obj = nullptr; UObject* Obj = nullptr;
UEdGraphPin* ResultPin = nullptr;
// The Starting Asset and the Editor we Opened
UObject* OriginalAsset = nullptr; UObject* OriginalAsset = nullptr;
IAssetEditorInstance* Editor = nullptr; IAssetEditorInstance* Editor = nullptr;
UEdGraphPin* ResultPin = nullptr;
MCPErrorCallback ErrorCB = nullptr; // True if an error has occurred.
TArray<UObject*> Chain; bool bError = false;
// Notifier for tracking touched objects.
MCPNotifier OwnedNotifier;
MCPNotifier* Notifier = &OwnedNotifier;
// Internal methods.
static bool StrEq(const FString& A, const TCHAR* B) { return A.Equals(B, ESearchCase::IgnoreCase); } static bool StrEq(const FString& A, const TCHAR* B) { return A.Equals(B, ESearchCase::IgnoreCase); }
void SetObj(UObject* InObj) { if (InObj) Chain.AddUnique(InObj); Obj = InObj; ResultPin = nullptr; } void SetObj(UObject* InObj) { Notifier->PreEditAddObject(InObj); Obj = InObj; ResultPin = nullptr; }
void SetPin(UEdGraphPin* InPin) { ResultPin = InPin; Obj = nullptr; } void SetPin(UEdGraphPin* InPin) { ResultPin = InPin; Obj = nullptr; }
MCPFetcher& SetError(const FString& Msg); MCPFetcher& SetError(const FString& Msg);
MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected); MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected);

View File

@@ -0,0 +1,28 @@
#pragma once
#include "CoreMinimal.h"
// Tracks objects that have been touched during an editing operation.
// Handles PreEditChange/PostEditChange, ReconstructNode, and other
// notifications that need to happen after modifications.
//
// Usage:
// MCPNotifier N;
// MCPFetcher F(Result, N);
// F.Walk(Path)...
// N.PreEdit();
// // modify stuff
// N.PostEdit();
//
class MCPNotifier
{
public:
void PreEditAddObject(UObject* Obj);
void PreEdit();
void PostEdit();
private:
bool bInsidePrePost = false;
TSet<UObject*> TouchedSet;
TArray<UObject*> TouchedArray;
};

View File

@@ -8,7 +8,7 @@
struct MCPProperty struct MCPProperty
{ {
FProperty* Prop = nullptr; FProperty* Prop = nullptr;
void* ValuePtr = nullptr; void* Container = nullptr;
MCPProperty() = default; MCPProperty() = default;
MCPProperty(FProperty* InProp, void* Container); MCPProperty(FProperty* InProp, void* Container);
@@ -18,4 +18,9 @@ struct MCPProperty
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 TArray<MCPProperty> GetAll(UObject* Obj, EPropertyFlags Flags);
static TArray<MCPProperty> GetAllSubstring(UObject* Obj, EPropertyFlags Flags, const FString& Substring);
static TArray<MCPProperty> GetAllExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name);
static MCPProperty GetOneExactMatch(UObject* Obj, EPropertyFlags Flags, const FString& Name, MCPErrorCallback Error);
}; };