Working on editing MG

This commit is contained in:
2026-03-12 21:31:41 -04:00
parent 9f90c14aa4
commit 8715cd25b0
9 changed files with 121 additions and 39 deletions

Binary file not shown.

Binary file not shown.

View File

@@ -34,22 +34,22 @@ class UMCP_GraphPin_Connect : public UObject, public IMCPHandler
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Description="Blueprint name or package path")) UPROPERTY(meta=(Description="Path to a graph, e.g. /Game/Foo,graph:EventGraph or /Game/Materials/M_Test"))
FString Blueprint; FString Graph;
UPROPERTY(meta=(Description="Array of {sourcePin, targetPin} objects. Each pin is a path like node:MyNode,pin:Output")) UPROPERTY(meta=(Description="Array of {sourcePin, targetPin} objects. Each pin is a path like node:MyNode,pin:Output"))
FMCPJsonArray Connections; FMCPJsonArray Connections;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("Connect pins between nodes in a Blueprint graph."); return TEXT("Connect pins between nodes in a graph (Blueprint or Material).");
} }
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F(Result);
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UEdGraph* G = F.Walk(Graph).ToGraph().Cast<UEdGraph>();
if (!BP) return; if (!G) return;
F.PreEdit(); F.PreEdit();
@@ -62,15 +62,15 @@ public:
if (!MCPUtils::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal, Result)) if (!MCPUtils::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal, Result))
continue; continue;
MCPFetcher FS(Result, BP); MCPFetcher FS(Result, G);
UEdGraphPin* SourcePin = FS.Walk(Entry.SourcePin).Cast<UEdGraphPin>(); UEdGraphPin* SourcePin = FS.Walk(Entry.SourcePin).Cast<UEdGraphPin>();
if (!SourcePin) continue; if (!SourcePin) continue;
MCPFetcher FT(Result, BP); MCPFetcher FT(Result, G);
UEdGraphPin* TargetPin = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>(); UEdGraphPin* TargetPin = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>();
if (!TargetPin) continue; if (!TargetPin) continue;
const UEdGraphSchema* Schema = SourcePin->GetOwningNode()->GetGraph()->GetSchema(); const UEdGraphSchema* Schema = G->GetSchema();
const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin); const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin);
if (Response.Response == CONNECT_RESPONSE_DISALLOW) if (Response.Response == CONNECT_RESPONSE_DISALLOW)
{ {

View File

@@ -33,23 +33,23 @@ class UMCP_GraphPin_Disconnect : public UObject, public IMCPHandler
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Description="Blueprint name or package path")) UPROPERTY(meta=(Description="Path to a graph, e.g. /Game/Foo,graph:EventGraph or /Game/Materials/M_Test"))
FString Blueprint; FString Graph;
UPROPERTY(meta=(Description="Array of {pin, targetPin?} objects. Each pin is a path like node:MyNode,pin:Output. If targetPin is omitted, all connections on the pin are broken.")) UPROPERTY(meta=(Description="Array of {pin, targetPin?} objects. Each pin is a path like node:MyNode,pin:Output. If targetPin is omitted, all connections on the pin are broken."))
FMCPJsonArray Disconnections; FMCPJsonArray Disconnections;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("Disconnect pins in a Blueprint graph. " return TEXT("Disconnect pins in a graph (Blueprint or Material). "
"Can disconnect a specific link or all links on a pin."); "Can disconnect a specific link or all links on a pin.");
} }
virtual void Handle(FStringBuilderBase& Result) override virtual void Handle(FStringBuilderBase& Result) override
{ {
MCPFetcher F(Result); MCPFetcher F(Result);
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>(); UEdGraph* G = F.Walk(Graph).ToGraph().Cast<UEdGraph>();
if (!BP) return; if (!G) return;
F.PreEdit(); F.PreEdit();
@@ -61,7 +61,7 @@ public:
FDisconnectPinEntry Entry; FDisconnectPinEntry Entry;
if (!MCPUtils::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal, Result)) continue; if (!MCPUtils::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal, Result)) continue;
MCPFetcher FP(Result, BP); MCPFetcher FP(Result, G);
UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast<UEdGraphPin>(); UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast<UEdGraphPin>();
if (!Pin) continue; if (!Pin) continue;
@@ -69,7 +69,7 @@ public:
if (!Entry.TargetPin.IsEmpty()) if (!Entry.TargetPin.IsEmpty())
{ {
MCPFetcher FT(Result, BP); MCPFetcher FT(Result, G);
UEdGraphPin* Target = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>(); UEdGraphPin* Target = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>();
if (!Target) continue; if (!Target) continue;

View File

@@ -76,23 +76,28 @@ public:
continue; continue;
} }
const UEdGraphSchema_K2* Schema = Cast<UEdGraphSchema_K2>(Node->GetGraph()->GetSchema()); Pin->Modify();
check(Schema);
// Parse and validate the value before applying. // K2 schemas support validation before setting; other schemas (e.g. material) don't.
const UEdGraphSchema_K2* K2Schema = Cast<UEdGraphSchema_K2>(Node->GetGraph()->GetSchema());
if (K2Schema)
{
FString UseDefaultValue; FString UseDefaultValue;
TObjectPtr<UObject> UseDefaultObject = nullptr; TObjectPtr<UObject> UseDefaultObject = nullptr;
FText UseDefaultText; FText UseDefaultText;
Schema->GetPinDefaultValuesFromString(Pin->PinType, Node, Entry.Value, UseDefaultValue, UseDefaultObject, UseDefaultText, false); K2Schema->GetPinDefaultValuesFromString(Pin->PinType, Node, Entry.Value, UseDefaultValue, UseDefaultObject, UseDefaultText, false);
FString Error = Schema->IsPinDefaultValid(Pin, UseDefaultValue, UseDefaultObject, UseDefaultText); FString Error = K2Schema->IsPinDefaultValid(Pin, UseDefaultValue, UseDefaultObject, UseDefaultText);
if (!Error.IsEmpty()) if (!Error.IsEmpty())
{ {
Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error); Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error);
continue; continue;
} }
K2Schema->TrySetDefaultValue(*Pin, Entry.Value);
Pin->Modify(); }
Schema->TrySetDefaultValue(*Pin, Entry.Value); else
{
Node->GetGraph()->GetSchema()->TrySetDefaultValue(*Pin, Entry.Value);
}
SuccessCount++; SuccessCount++;
ModifiedNodes.Add(Node); ModifiedNodes.Add(Node);

View File

@@ -10,6 +10,7 @@
#include "K2Node_VariableGet.h" #include "K2Node_VariableGet.h"
#include "K2Node_CallFunction.h" #include "K2Node_CallFunction.h"
#include "K2Node_FunctionEntry.h" #include "K2Node_FunctionEntry.h"
#include "MaterialGraph/MaterialGraphNode.h"
static FString WrapText(const FString& Text, int32 ColLimit, const FString& Prefix) static FString WrapText(const FString& Text, int32 ColLimit, const FString& Prefix)
{ {
@@ -232,6 +233,9 @@ void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
Output.Appendf(TEXT("\nnode %s: %s\n"), *MCPUtils::FormatName(Node), *MCPUtils::FormatNodeTitle(Node)); Output.Appendf(TEXT("\nnode %s: %s\n"), *MCPUtils::FormatName(Node), *MCPUtils::FormatNodeTitle(Node));
// Emit material expression properties (if applicable).
EmitMaterialProperties(Node, Output, true);
// Emit input data pins. // Emit input data pins.
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input)) for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
{ {
@@ -274,6 +278,39 @@ void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
} }
} }
void FlxBlueprintExporter::EmitMaterialProperty(UMaterialExpression* Expression, FProperty* Prop, FStringBuilderBase& Out)
{
FString ValueStr = MCPUtils::GetPropertyValueText(Expression, Prop);
ValueStr.ReplaceInline(TEXT("\r\n"), TEXT(" "));
ValueStr.ReplaceInline(TEXT("\n"), TEXT(" "));
if (ValueStr.Len() > 80)
ValueStr = ValueStr.Left(80) + TEXT("...");
bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst);
Out.Appendf(TEXT(" %s %s %s = %s\n"),
bEditable ? TEXT("editable") : TEXT("readonly"),
*MCPUtils::FormatPropertyType(Prop),
*MCPUtils::FormatName(Prop),
*ValueStr);
}
void FlxBlueprintExporter::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary)
{
UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node);
if (!MatNode || !MatNode->MaterialExpression) return;
UMaterialExpression* Expression = MatNode->MaterialExpression;
FString PrimaryCategory = Expression->GetClass()->GetName();
TArray<FProperty*> Props = MCPUtils::SearchProperties(Expression, FString(), CPF_Edit, false);
for (FProperty* Prop : Props)
{
FString Category = Prop->HasMetaData(TEXT("Category")) ? Prop->GetMetaData(TEXT("Category")) : FString();
if ((Category == PrimaryCategory) == bPrimary)
EmitMaterialProperty(Expression, Prop, Out);
}
}
void FlxBlueprintExporter::EmitLocalVariables() void FlxBlueprintExporter::EmitLocalVariables()
{ {
for (UEdGraphNode* Node : Graph->Nodes) for (UEdGraphNode* Node : Graph->Nodes)
@@ -312,6 +349,8 @@ void FlxBlueprintExporter::EmitDetails()
if (Node->IsA<UK2Node_VariableGet>()) continue; if (Node->IsA<UK2Node_VariableGet>()) continue;
Details.Appendf(TEXT("details %s\n"), *MCPUtils::FormatName(Node)); Details.Appendf(TEXT("details %s\n"), *MCPUtils::FormatName(Node));
Details.Appendf(TEXT(" pos %d, %d\n"), Node->NodePosX, Node->NodePosY); Details.Appendf(TEXT(" pos %d, %d\n"), Node->NodePosX, Node->NodePosY);
EmitMaterialProperties(Node, Details, false);
} }
} }
@@ -327,7 +366,7 @@ void FlxBlueprintExporter::EmitComments()
int32 CH = CommentNode->NodeHeight; int32 CH = CommentNode->NodeHeight;
// Emit header. // Emit header.
Output.Append(TEXT("\ncomment:\n")); Output.Appendf(TEXT("\ncomment %s:\n"), *MCPUtils::FormatName(CommentNode));
// Emit wrapped, indented body. // Emit wrapped, indented body.
Output.Append(WrapText(CommentNode->NodeComment, 70, TEXT(" - "))); Output.Append(WrapText(CommentNode->NodeComment, 70, TEXT(" - ")));

View File

@@ -267,6 +267,42 @@ MCPFetcher& MCPFetcher::Template()
return *this; return *this;
} }
MCPFetcher& MCPFetcher::ToBlueprint()
{
if (bError) return *this;
if (::Cast<UBlueprint>(Obj)) return *this;
if (UWorld* World = ::Cast<UWorld>(Obj))
{
if (!World->PersistentLevel)
return SetError(TEXT("ToBlueprint: World has no PersistentLevel"));
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
if (!LevelBP)
return SetError(TEXT("ToBlueprint: World has no level blueprint"));
SetObj(LevelBP);
return *this;
}
return TypeMismatch(TEXT("ToBlueprint"), TEXT("Blueprint or World"));
}
MCPFetcher& MCPFetcher::ToGraph()
{
if (bError) return *this;
if (::Cast<UEdGraph>(Obj)) return *this;
if (UMaterial* Mat = ::Cast<UMaterial>(Obj))
{
MCPUtils::EnsureMaterialGraph(Mat);
if (!Mat->MaterialGraph)
return SetError(FString::Printf(TEXT("ToGraph: Material '%s' has no material graph"), *Mat->GetName()));
SetObj(Mat->MaterialGraph);
return *this;
}
return TypeMismatch(TEXT("ToGraph"), TEXT("Graph or Material"));
}
MCPFetcher& MCPFetcher::MatExp(const FString& Value) MCPFetcher& MCPFetcher::MatExp(const FString& Value)
{ {
if (bError) return *this; if (bError) return *this;

View File

@@ -6,6 +6,8 @@
#include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h" #include "EdGraph/EdGraphPin.h"
class UMaterialExpression;
class FlxBlueprintExporter class FlxBlueprintExporter
{ {
public: public:
@@ -60,6 +62,8 @@ private:
void Traverse(UEdGraphNode* Node); void Traverse(UEdGraphNode* Node);
void SortNodes(); void SortNodes();
void EmitNode(UEdGraphNode* Node); void EmitNode(UEdGraphNode* Node);
void EmitMaterialProperty(UMaterialExpression* Expression, FProperty* Prop, FStringBuilderBase& Out);
void EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary);
void EmitLocalVariables(); void EmitLocalVariables();
void EmitGraph(); void EmitGraph();
void EmitDetails(); void EmitDetails();

View File

@@ -53,6 +53,10 @@ public:
// (e.g. Blueprint → CDO, everything else → as-is). // (e.g. Blueprint → CDO, everything else → as-is).
MCPFetcher& Template(); MCPFetcher& Template();
// C++-only navigation: drill down to a specific type.
MCPFetcher& ToBlueprint();
MCPFetcher& ToGraph();
// Walker table entry. // Walker table entry.
struct FWalker struct FWalker
{ {