diff --git a/Content/Testing/M_Test_Inst.uasset b/Content/Testing/M_Test_Inst.uasset deleted file mode 100644 index e0366443..00000000 --- a/Content/Testing/M_Test_Inst.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:707150b1787ffacc956c89e36ed76efc536125b266cf8fca35a6bfc447875e08 -size 8986 diff --git a/Content/Testing/M_Test_Inst_Inst.uasset b/Content/Testing/M_Test_Inst_Inst.uasset deleted file mode 100644 index b7a4c91a..00000000 --- a/Content/Testing/M_Test_Inst_Inst.uasset +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ef57ab84d7e785519670ccd29b3f272fc902c16a2a22fc208a5b835d16027b1c -size 9021 diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h index 46a53897..91191df2 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h @@ -34,22 +34,22 @@ class UMCP_GraphPin_Connect : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Blueprint name or package path")) - FString Blueprint; + UPROPERTY(meta=(Description="Path to a graph, e.g. /Game/Foo,graph:EventGraph or /Game/Materials/M_Test")) + FString Graph; UPROPERTY(meta=(Description="Array of {sourcePin, targetPin} objects. Each pin is a path like node:MyNode,pin:Output")) FMCPJsonArray Connections; 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 { MCPFetcher F(Result); - UBlueprint* BP = F.Walk(Blueprint).Cast(); - if (!BP) return; + UEdGraph* G = F.Walk(Graph).ToGraph().Cast(); + if (!G) return; F.PreEdit(); @@ -62,15 +62,15 @@ public: if (!MCPUtils::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal, Result)) continue; - MCPFetcher FS(Result, BP); + MCPFetcher FS(Result, G); UEdGraphPin* SourcePin = FS.Walk(Entry.SourcePin).Cast(); if (!SourcePin) continue; - MCPFetcher FT(Result, BP); + MCPFetcher FT(Result, G); UEdGraphPin* TargetPin = FT.Walk(Entry.TargetPin).Cast(); if (!TargetPin) continue; - const UEdGraphSchema* Schema = SourcePin->GetOwningNode()->GetGraph()->GetSchema(); + const UEdGraphSchema* Schema = G->GetSchema(); const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin); if (Response.Response == CONNECT_RESPONSE_DISALLOW) { diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h index 9dd48f32..e04fef1f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h @@ -33,23 +33,23 @@ class UMCP_GraphPin_Disconnect : public UObject, public IMCPHandler GENERATED_BODY() public: - UPROPERTY(meta=(Description="Blueprint name or package path")) - FString Blueprint; + UPROPERTY(meta=(Description="Path to a graph, e.g. /Game/Foo,graph:EventGraph or /Game/Materials/M_Test")) + 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.")) FMCPJsonArray Disconnections; 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."); } virtual void Handle(FStringBuilderBase& Result) override { MCPFetcher F(Result); - UBlueprint* BP = F.Walk(Blueprint).Cast(); - if (!BP) return; + UEdGraph* G = F.Walk(Graph).ToGraph().Cast(); + if (!G) return; F.PreEdit(); @@ -61,7 +61,7 @@ public: FDisconnectPinEntry Entry; if (!MCPUtils::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal, Result)) continue; - MCPFetcher FP(Result, BP); + MCPFetcher FP(Result, G); UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast(); if (!Pin) continue; @@ -69,7 +69,7 @@ public: if (!Entry.TargetPin.IsEmpty()) { - MCPFetcher FT(Result, BP); + MCPFetcher FT(Result, G); UEdGraphPin* Target = FT.Walk(Entry.TargetPin).Cast(); if (!Target) continue; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_SetDefault.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_SetDefault.h index 8d93f019..10a9fa22 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_SetDefault.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_SetDefault.h @@ -76,23 +76,28 @@ public: continue; } - const UEdGraphSchema_K2* Schema = Cast(Node->GetGraph()->GetSchema()); - check(Schema); - - // Parse and validate the value before applying. - FString UseDefaultValue; - TObjectPtr UseDefaultObject = nullptr; - FText UseDefaultText; - Schema->GetPinDefaultValuesFromString(Pin->PinType, Node, Entry.Value, UseDefaultValue, UseDefaultObject, UseDefaultText, false); - FString Error = Schema->IsPinDefaultValid(Pin, UseDefaultValue, UseDefaultObject, UseDefaultText); - if (!Error.IsEmpty()) - { - Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error); - continue; - } - Pin->Modify(); - Schema->TrySetDefaultValue(*Pin, Entry.Value); + + // K2 schemas support validation before setting; other schemas (e.g. material) don't. + const UEdGraphSchema_K2* K2Schema = Cast(Node->GetGraph()->GetSchema()); + if (K2Schema) + { + FString UseDefaultValue; + TObjectPtr UseDefaultObject = nullptr; + FText UseDefaultText; + K2Schema->GetPinDefaultValuesFromString(Pin->PinType, Node, Entry.Value, UseDefaultValue, UseDefaultObject, UseDefaultText, false); + FString Error = K2Schema->IsPinDefaultValid(Pin, UseDefaultValue, UseDefaultObject, UseDefaultText); + if (!Error.IsEmpty()) + { + Result.Appendf(TEXT("error: %s: %s\n"), *MCPUtils::FormatName(Pin), *Error); + continue; + } + K2Schema->TrySetDefaultValue(*Pin, Entry.Value); + } + else + { + Node->GetGraph()->GetSchema()->TrySetDefaultValue(*Pin, Entry.Value); + } SuccessCount++; ModifiedNodes.Add(Node); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintExporter.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintExporter.cpp index d1247046..5cf15259 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintExporter.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/BlueprintExporter.cpp @@ -10,6 +10,7 @@ #include "K2Node_VariableGet.h" #include "K2Node_CallFunction.h" #include "K2Node_FunctionEntry.h" +#include "MaterialGraph/MaterialGraphNode.h" static FString WrapText(const FString& Text, int32 ColLimit, const FString& Prefix) { @@ -47,7 +48,7 @@ FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph) : Graph(InGraph) { SortNodes(); -EmitLocalVariables(); + EmitLocalVariables(); EmitDetails(); EmitGraph(); EmitComments(); @@ -232,6 +233,9 @@ void FlxBlueprintExporter::EmitNode(UEdGraphNode* 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. 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(Node); + if (!MatNode || !MatNode->MaterialExpression) return; + + UMaterialExpression* Expression = MatNode->MaterialExpression; + FString PrimaryCategory = Expression->GetClass()->GetName(); + TArray 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() { for (UEdGraphNode* Node : Graph->Nodes) @@ -312,6 +349,8 @@ void FlxBlueprintExporter::EmitDetails() if (Node->IsA()) continue; Details.Appendf(TEXT("details %s\n"), *MCPUtils::FormatName(Node)); 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; // Emit header. - Output.Append(TEXT("\ncomment:\n")); + Output.Appendf(TEXT("\ncomment %s:\n"), *MCPUtils::FormatName(CommentNode)); // Emit wrapped, indented body. Output.Append(WrapText(CommentNode->NodeComment, 70, TEXT(" - "))); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp index d49a502b..156e06e0 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp @@ -267,6 +267,42 @@ MCPFetcher& MCPFetcher::Template() return *this; } +MCPFetcher& MCPFetcher::ToBlueprint() +{ + if (bError) return *this; + if (::Cast(Obj)) return *this; + + if (UWorld* World = ::Cast(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(Obj)) return *this; + + if (UMaterial* Mat = ::Cast(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) { if (bError) return *this; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/BlueprintExporter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/BlueprintExporter.h index aa414066..bd20edc8 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/BlueprintExporter.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/BlueprintExporter.h @@ -6,6 +6,8 @@ #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" +class UMaterialExpression; + class FlxBlueprintExporter { public: @@ -60,6 +62,8 @@ private: void Traverse(UEdGraphNode* Node); void SortNodes(); void EmitNode(UEdGraphNode* Node); + void EmitMaterialProperty(UMaterialExpression* Expression, FProperty* Prop, FStringBuilderBase& Out); + void EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary); void EmitLocalVariables(); void EmitGraph(); void EmitDetails(); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h index 608553cc..46989eff 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h @@ -53,6 +53,10 @@ public: // (e.g. Blueprint → CDO, everything else → as-is). MCPFetcher& Template(); + // C++-only navigation: drill down to a specific type. + MCPFetcher& ToBlueprint(); + MCPFetcher& ToGraph(); + // Walker table entry. struct FWalker {