diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_SetVariableMetadata.h b/Plugins/BlueprintMCP/Deprecated/Blueprint_SetVariableMetadata.h similarity index 97% rename from Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_SetVariableMetadata.h rename to Plugins/BlueprintMCP/Deprecated/Blueprint_SetVariableMetadata.h index 3d9ed930..6cfaa263 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_SetVariableMetadata.h +++ b/Plugins/BlueprintMCP/Deprecated/Blueprint_SetVariableMetadata.h @@ -80,6 +80,7 @@ public: FName VarFName = VarDesc->VarName; int32 ChangeCount = 0; + F.PreEdit(); // Category if (Json->HasField(TEXT("category"))) @@ -179,9 +180,7 @@ public: return; } - BP->PreEditChange(nullptr); - BP->PostEditChange(); - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + F.PostEdit(); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Updated %d field(s) on %s in %s.%s\n"), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/AnimBlueprint_SetBlendSpaceSamples.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/AnimBlueprint_SetBlendSpaceSamples.h index c3a0ae9a..dc91cdae 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/AnimBlueprint_SetBlendSpaceSamples.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/AnimBlueprint_SetBlendSpaceSamples.h @@ -73,7 +73,7 @@ public: UBlendSpace* BS = Assets.Object(); // Set axis parameters - BS->PreEditChange(nullptr); + MCPUtils::PreEdit({BS}); const FBlendParameter& ParamX = BS->GetBlendParameter(0); const FBlendParameter& ParamY = BS->GetBlendParameter(1); @@ -126,10 +126,9 @@ public: } BS->ValidateSampleData(); - BS->PostEditChange(); + MCPUtils::PostEdit({BS}); // Save - BS->MarkPackageDirty(); bool bSaved = MCPUtils::SaveGenericPackage(BS); Result.Appendf(TEXT("Set %d samples on %s\n"), SamplesSet, *MCPUtils::FormatName(BS)); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlendSpace_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlendSpace_Create.h index eb93458e..5cd7f568 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlendSpace_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlendSpace_Create.h @@ -70,7 +70,6 @@ public: // Set skeleton. NewBS->SetSkeleton(SkeletonObj); - // Mark dirty and save. NewBS->MarkPackageDirty(); bool bSaved = MCPUtils::SaveGenericPackage(NewBS); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Create.h index d73f0620..a139a31f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Create.h @@ -69,6 +69,8 @@ public: } } + F.PreEdit(); + if (GraphType == TEXT("function")) { UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, FName(*Graph), @@ -114,7 +116,7 @@ public: Result.Appendf(TEXT("Created custom event: %s\n"), *MCPUtils::FormatName(NewEvent)); } - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + F.PostEdit(); MCPUtils::SaveBlueprintPackage(BP); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Delete.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Delete.h index 4136e207..8bcb911b 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Delete.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Delete.h @@ -85,8 +85,9 @@ public: // Remove the graph FString GraphName = MCPUtils::FormatName(TargetGraph); + F.PreEdit(); FBlueprintEditorUtils::RemoveGraph(BP, TargetGraph, EGraphRemoveFlags::Default); - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + F.PostEdit(); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Deleted %s graph %s\n"), *GraphType, *GraphName); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Rename.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Rename.h index 930dc802..559fa162 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Rename.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/BlueprintGraph_Rename.h @@ -73,8 +73,9 @@ public: } } + F.PreEdit(); FBlueprintEditorUtils::RenameGraph(TargetGraph, NewName); - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + F.PostEdit(); MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Renamed to %s %s\n"), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddComponent.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddComponent.h index 104a25dc..60742b34 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddComponent.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddComponent.h @@ -8,7 +8,6 @@ #include "Engine/SimpleConstructionScript.h" #include "Engine/SCS_Node.h" #include "Components/ActorComponent.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "Blueprint_AddComponent.generated.h" @@ -107,6 +106,7 @@ public: } // Create the SCS node + MCPUtils::PreEdit({BP}); USCS_Node* NewNode = SCS->CreateNode(ComponentClassObj, FName(*Component)); if (!NewNode) { @@ -126,7 +126,7 @@ public: SCS->AddNode(NewNode); } - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + MCPUtils::PostEdit({BP}); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Added component %s (%s)"), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddEventDispatcher.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddEventDispatcher.h index b6f435c5..361eaeb1 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddEventDispatcher.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddEventDispatcher.h @@ -75,6 +75,7 @@ public: } // Add a member variable with PC_MCDelegate pin type + F.PreEdit(); FEdGraphPinType DelegateType; DelegateType.PinCategory = UEdGraphSchema_K2::PC_MCDelegate; if (!FBlueprintEditorUtils::AddMemberVariable(BP, DispatcherFName, DelegateType)) @@ -115,7 +116,7 @@ public: if (!EntryNode) { - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + F.PostEdit(); MCPUtils::SaveBlueprintPackage(BP); Result.Append(TEXT("Error: Event dispatcher created but entry node not found — parameters could not be added.\n")); return; @@ -136,7 +137,7 @@ public: } } - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + F.PostEdit(); MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Created event dispatcher '%s'"), *DispatcherName); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddFunctionParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddFunctionParameter.h index d185cc84..2a11ac4f 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddFunctionParameter.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddFunctionParameter.h @@ -133,9 +133,9 @@ public: } // Add the parameter pin (EGPD_Output on entry = input to callers) + MCPUtils::PreEdit({BP}); EntryNode->CreateUserDefinedPin(FName(*ParamName), PinType, EGPD_Output); - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + MCPUtils::PostEdit({BP}); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Added %s parameter '%s' to %s '%s'%s\n"), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddInterface.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddInterface.h index 86e8ce8e..f949a30d 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddInterface.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddInterface.h @@ -55,6 +55,7 @@ public: } FTopLevelAssetPath InterfacePath = InterfaceClass->GetClassPathName(); + MCPUtils::PreEdit({BP}); bool bAdded = FBlueprintEditorUtils::ImplementNewInterface(BP, InterfacePath); if (!bAdded) { @@ -65,7 +66,7 @@ public: } // Collect stub function graph names from the newly added interface entry - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + MCPUtils::PostEdit({BP}); Result.Appendf(TEXT("Added interface %s\n"), *MCPUtils::FormatName(InterfaceClass)); Result.Appendf(TEXT("Function stubs:\n")); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddVariable.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddVariable.h index 95360404..0c9e4ad1 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddVariable.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_AddVariable.h @@ -69,6 +69,7 @@ public: PinType.ContainerType = EPinContainerType::Array; // Add the variable + MCPUtils::PreEdit({BP}); if (!FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, PinType, DefaultValue)) { MCPErrorCallback(Result).SetError(FString::Printf( @@ -79,7 +80,7 @@ public: if (!Category.IsEmpty()) FBlueprintEditorUtils::SetBlueprintVariableCategory(BP, VarFName, nullptr, FText::FromString(Category)); - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + MCPUtils::PostEdit({BP}); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Added %s %s to %s\n"), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_ChangeFunctionParameterType.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_ChangeFunctionParameterType.h index ad2bca95..9a2a85d7 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_ChangeFunctionParameterType.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_ChangeFunctionParameterType.h @@ -135,7 +135,8 @@ public: return; } - // Apply the type change + // Apply the type change (PreEdit/PostEdit on the node itself, not the BP — + // MCPUtils::PreEdit/PostEdit operates at BP level, not node level) EntryNode->PreEditChange(nullptr); (*FoundPinInfo)->PinType = NewPinType; EntryNode->PostEditChange(); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_ChangeVariableType.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_ChangeVariableType.h index 2385c43a..faccd0d1 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_ChangeVariableType.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_ChangeVariableType.h @@ -9,7 +9,6 @@ #include "EdGraph/EdGraphPin.h" #include "K2Node_VariableGet.h" #include "K2Node_VariableSet.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "Blueprint_ChangeVariableType.generated.h" @@ -129,9 +128,9 @@ public: } // Apply the type change - BP->PreEditChange(nullptr); + F.PreEdit(); Found->VarType = NewPinType; - BP->PostEditChange(); + F.PostEdit(); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Changed %s to %s.%s\n"), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RefreshAllNodes.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RefreshAllNodes.h index 30190b7d..9a7e0840 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RefreshAllNodes.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RefreshAllNodes.h @@ -42,6 +42,7 @@ public: int32 NodeCount = MCPUtils::AllNodes(BP).Num(); // Refresh all nodes + MCPUtils::PreEdit({BP}); FBlueprintEditorUtils::RefreshAllNodes(BP); // Remove orphaned pins from all nodes @@ -61,7 +62,7 @@ public: } // Mark as modified and recompile after orphan removal - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + MCPUtils::PostEdit({BP}); // Summary Result.Appendf(TEXT("Refreshed %s: %d graphs, %d nodes"), *MCPUtils::FormatName(BP), GraphCount, NodeCount); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveComponent.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveComponent.h index 6b7d45be..33b42411 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveComponent.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveComponent.h @@ -7,7 +7,6 @@ #include "Engine/Blueprint.h" #include "Engine/SimpleConstructionScript.h" #include "Engine/SCS_Node.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "Blueprint_RemoveComponent.generated.h" @@ -84,9 +83,10 @@ public: FString RemovedName = MCPUtils::FormatName(NodeToRemove->ComponentTemplate); // Remove the node (promotes children to parent if it has any — but we've guarded root above) + F.PreEdit(); SCS->RemoveNodeAndPromoteChildren(NodeToRemove); + F.PostEdit(); - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Removed component %s.%s\n"), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveInterface.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveInterface.h index 8a4d8487..f9967ed8 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveInterface.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveInterface.h @@ -65,8 +65,9 @@ public: } FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName(); + MCPUtils::PreEdit({BP}); FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions); - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + MCPUtils::PostEdit({BP}); Result.Appendf(TEXT("Removed interface %s\n"), *MCPUtils::FormatName(FoundInterface)); if (PreserveFunctions) diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveVariable.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveVariable.h index 7f70a416..8d68fe54 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveVariable.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_RemoveVariable.h @@ -62,8 +62,9 @@ public: FName VarFName = Found->VarName; // RemoveMemberVariable also cleans up Get/Set nodes + F.PreEdit(); FBlueprintEditorUtils::RemoveMemberVariable(BP, VarFName); - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + F.PostEdit(); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); Result.Appendf(TEXT("Removed variable %s from %s.%s\n"), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Reparent.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Reparent.h index ace50b8b..1d4a89a0 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Reparent.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Blueprint_Reparent.h @@ -61,9 +61,9 @@ public: } // Perform reparent - BP->PreEditChange(nullptr); + MCPUtils::PreEdit({BP}); BP->ParentClass = NewParentClassObj; - BP->PostEditChange(); + MCPUtils::PostEdit({BP}); FBlueprintEditorUtils::RefreshAllNodes(BP); FKismetEditorUtilities::CompileBlueprint(BP); bool bSaved = MCPUtils::SaveBlueprintPackage(BP); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Create.h index c6a423f2..c8bea8ee 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Create.h @@ -4,11 +4,9 @@ #include "MCPHandler.h" #include "MCPFetcher.h" #include "MCPUtils.h" -#include "Engine/Blueprint.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphSchema.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "GraphNode_Create.generated.h" @@ -57,6 +55,7 @@ public: UEdGraph* TargetGraph = F.Walk(Graph).Cast(); if (!TargetGraph) return; + F.PreEdit(); int32 SuccessCount = 0; int32 TotalCount = Nodes.Array.Num(); @@ -98,16 +97,7 @@ public: SuccessCount++; } - // Mark the owning asset as modified - UBlueprint* BP = Cast(TargetGraph->GetOuter()); - if (BP) - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); - - TargetGraph->NotifyGraphChanged(); - - UObject* Outer = TargetGraph->GetOuter(); - if (Outer) - Outer->MarkPackageDirty(); + F.PostEdit(); Result.Appendf(TEXT("Spawned %d/%d nodes.\n"), SuccessCount, TotalCount); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Delete.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Delete.h index 1c3defea..e05cfec3 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Delete.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Delete.h @@ -5,10 +5,6 @@ #include "MCPFetcher.h" #include "MCPUtils.h" #include "EdGraph/EdGraphNode.h" -#include "K2Node_Event.h" -#include "K2Node_CustomEvent.h" -#include "K2Node_FunctionEntry.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "GraphNode_Delete.generated.h" @@ -27,8 +23,8 @@ public: virtual FString GetDescription() const override { - return TEXT("Delete a node from a Blueprint graph. " - "Cannot delete entry nodes (FunctionEntry, Event, CustomEvent)."); + return TEXT("Delete a node from a graph. " + "Cannot delete undeletable nodes (entry points, root nodes, etc)."); } virtual void Handle(const FJsonObject* Json, FStringBuilderBase& Result) override @@ -38,42 +34,21 @@ public: if (!FoundNode) return; UEdGraph* Graph = FoundNode->GetGraph(); - UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForNodeChecked(FoundNode); - - // Protect root/entry nodes — deleting these leaves the graph in an invalid - // state with no root node, causing compiler errors that can't be fixed - // without recreating the entire function/event. - MCPErrorCallback Error(Result); FString NodeTitle = MCPUtils::FormatName(FoundNode); FString GraphName = MCPUtils::FormatName(Graph); - if (Cast(FoundNode)) + if (!FoundNode->CanUserDeleteNode()) { + MCPErrorCallback Error(Result); return Error.SetError(FString::Printf( - TEXT("Cannot delete FunctionEntry node '%s' in graph '%s'. ") - TEXT("This is the root node of the function — removing it would leave an empty, uncompilable graph. ") - TEXT("To remove the entire function, delete it from the Blueprint editor."), - *NodeTitle, *GraphName)); - } - if (Cast(FoundNode)) - { - return Error.SetError(FString::Printf( - TEXT("Cannot delete event entry node '%s' in graph '%s'. ") - TEXT("This is the root node of the event handler — removing it would leave an empty, uncompilable graph."), - *NodeTitle, *GraphName)); - } - if (Cast(FoundNode)) - { - return Error.SetError(FString::Printf( - TEXT("Cannot delete CustomEvent entry node '%s' in graph '%s'. ") - TEXT("This is the root node of the custom event — removing it would leave an empty, uncompilable graph."), + TEXT("Cannot delete node '%s' in graph '%s' — it is not deletable."), *NodeTitle, *GraphName)); } + F.PreEdit(); FoundNode->BreakAllNodeLinks(); Graph->RemoveNode(FoundNode); - - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + F.PostEdit(); Result.Appendf(TEXT("Deleted node '%s' from graph '%s'.\n"), *NodeTitle, *GraphName); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Duplicate.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Duplicate.h index 394b01ee..8bce4e03 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Duplicate.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_Duplicate.h @@ -4,11 +4,9 @@ #include "MCPHandler.h" #include "MCPFetcher.h" #include "MCPUtils.h" -#include "Engine/Blueprint.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "GraphNode_Duplicate.generated.h" @@ -77,6 +75,7 @@ public: if (SourceNodes.Num() == 0) return; + F.PreEdit(); // Duplicate each node for (UEdGraphNode* SourceNode : SourceNodes) { @@ -101,8 +100,6 @@ public: Result.Appendf(TEXT("Duplicated: %s -> %s\n"), *MCPUtils::FormatName(SourceNode), *MCPUtils::FormatName(NewNode)); } - UBlueprint* BP = Cast(TargetGraph->GetOuter()); - if (BP) - FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); + F.PostEdit(); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_SetComment.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_SetComment.h index 70b96579..fd7b971c 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_SetComment.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_SetComment.h @@ -5,7 +5,6 @@ #include "MCPFetcher.h" #include "MCPUtils.h" #include "EdGraph/EdGraphNode.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "GraphNode_SetComment.generated.h" @@ -36,6 +35,8 @@ public: UEdGraphNode* FoundNode = F.Walk(Node).Cast(); if (!FoundNode) return; + F.PreEdit(); + FoundNode->NodeComment = Comment; // Make the comment bubble visible if setting a non-empty comment @@ -45,8 +46,7 @@ public: FoundNode->bCommentBubblePinned = true; } - UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForNodeChecked(FoundNode); - FBlueprintEditorUtils::MarkBlueprintAsModified(BP); + F.PostEdit(); Result.Appendf(TEXT("Comment set on %s\n"), *MCPUtils::FormatName(FoundNode)); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_SetPositions.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_SetPositions.h index 731048e4..3998891a 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_SetPositions.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphNode_SetPositions.h @@ -6,7 +6,6 @@ #include "MCPUtils.h" #include "Engine/Blueprint.h" #include "EdGraph/EdGraphNode.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "GraphNode_SetPositions.generated.h" @@ -53,6 +52,8 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; + F.PreEdit(); + int32 SuccessCount = 0; for (const TSharedPtr& NodeVal : Nodes.Array) @@ -70,7 +71,7 @@ public: SuccessCount++; } - FBlueprintEditorUtils::MarkBlueprintAsModified(BP); + F.PostEdit(); Result.Appendf(TEXT("Moved %d/%d nodes.\n"), SuccessCount, Nodes.Array.Num()); } }; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h index de3ddcd1..84d88a9a 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Connect.h @@ -5,7 +5,6 @@ #include "MCPFetcher.h" #include "MCPUtils.h" #include "EdGraph/EdGraphPin.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "GraphPin_Connect.generated.h" @@ -49,6 +48,8 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; + F.PreEdit(); + int32 SuccessCount = 0; int32 TotalCount = Connections.Array.Num(); @@ -81,10 +82,7 @@ public: SuccessCount++; } - if (SuccessCount > 0) - { - FBlueprintEditorUtils::MarkBlueprintAsModified(BP); - } + F.PostEdit(); Result.Appendf(TEXT("Connected %d/%d pins.\n"), SuccessCount, TotalCount); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h index 1ea2d907..516dfbbd 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_Disconnect.h @@ -5,7 +5,6 @@ #include "MCPFetcher.h" #include "MCPUtils.h" #include "EdGraph/EdGraphPin.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "GraphPin_Disconnect.generated.h" @@ -50,6 +49,8 @@ public: UBlueprint* BP = F.Walk(Blueprint).Cast(); if (!BP) return; + F.PreEdit(); + int32 SuccessCount = 0; int32 TotalDisconnected = 0; @@ -97,10 +98,7 @@ public: TotalDisconnected += DisconnectedCount; } - if (TotalDisconnected > 0) - { - FBlueprintEditorUtils::MarkBlueprintAsModified(BP); - } + F.PostEdit(); Result.Appendf(TEXT("Done: %d/%d succeeded, %d links broken.\n"), SuccessCount, Disconnections.Array.Num(), TotalDisconnected); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_SetDefault.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_SetDefault.h index 976ad7ce..5ac2c925 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_SetDefault.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/GraphPin_SetDefault.h @@ -5,7 +5,6 @@ #include "MCPFetcher.h" #include "MCPUtils.h" #include "EdGraph/EdGraphPin.h" -#include "Kismet2/BlueprintEditorUtils.h" #include "GraphPin_SetDefault.generated.h" diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_Create.h index 6fd73d10..ca4f1f62 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/MaterialInstance_Create.h @@ -91,9 +91,10 @@ public: } // Set parent. - MI->PreEditChange(nullptr); + TArray Chain = { MI }; + MCPUtils::PreEdit(Chain); MI->Parent = ParentMaterialObj; - MI->PostEditChange(); + MCPUtils::PostEdit(Chain); // Save. bool bSaved = MCPUtils::SaveGenericPackage(MI); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Compile.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Compile.h index f563f6f6..206748c7 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Compile.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Compile.h @@ -34,9 +34,10 @@ public: if (!Assets.Exact(Material).Errors(Result).ENone().ETwo().Load()) return; UMaterial* MaterialObj = Assets.Object(); - // Force recompile by triggering PreEditChange/PostEditChange - MaterialObj->PreEditChange(nullptr); - MaterialObj->PostEditChange(); + // Force recompile by triggering PreEdit/PostEdit + TArray Chain = { MaterialObj }; + MCPUtils::PreEdit(Chain); + MCPUtils::PostEdit(Chain); // Check for compilation errors via FMaterialResource on current platform TArray Errors; diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h index a9b4c793..6daaa768 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_Create.h @@ -84,7 +84,8 @@ public: // Apply optional properties. bool bHasTwoSided = Json->HasField(TEXT("twoSided")); - MaterialObj->PreEditChange(nullptr); + TArray Chain = { MaterialObj }; + MCPUtils::PreEdit(Chain); if (!Domain.IsEmpty()) MaterialObj->MaterialDomain = ParsedDomain; @@ -95,7 +96,7 @@ public: if (bHasTwoSided) MaterialObj->TwoSided = TwoSided; - MaterialObj->PostEditChange(); + MCPUtils::PostEdit(Chain); bool bSaved = MCPUtils::SaveMaterialPackage(MaterialObj); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_ReparentInstance.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_ReparentInstance.h index 2848f52d..e1cb50f0 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_ReparentInstance.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_ReparentInstance.h @@ -74,9 +74,9 @@ public: return; } - MI->PreEditChange(nullptr); + MCPUtils::PreEdit({MI}); MI->Parent = NewParentObj; - MI->PostEditChange(); + MCPUtils::PostEdit({MI}); MCPUtils::SaveGenericPackage(MI); Result.Appendf(TEXT("Reparented %s: %s -> %s\n"), diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetInstanceParameter.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetInstanceParameter.h index f9b3c52f..99481852 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetInstanceParameter.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetInstanceParameter.h @@ -159,9 +159,8 @@ public: if (!DryRun) { - MI->PreEditChange(nullptr); - MI->PostEditChange(); - MI->MarkPackageDirty(); + MCPUtils::PreEdit({MI}); + MCPUtils::PostEdit({MI}); MCPUtils::SaveGenericPackage(MI); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetProperty.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetProperty.h index 1c3fb4df..61ce53ff 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetProperty.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/Material_SetProperty.h @@ -55,7 +55,7 @@ public: bool bValue = Json->GetBoolField(TEXT("value")); OldValue = Getter() ? TEXT("true") : TEXT("false"); NewValue = bValue ? TEXT("true") : TEXT("false"); - if (!DryRun) { Mat->PreEditChange(nullptr); Setter(bValue); Mat->PostEditChange(); } + if (!DryRun) Setter(bValue); }; if (Property == TEXT("domain")) @@ -65,7 +65,7 @@ public: EMaterialDomain NewDomain; if (!MCPUtils::StringToEnum(ValueStr, NewDomain, Result, TEXT("MD_"))) return; NewValue = MCPUtils::EnumToString(NewDomain, TEXT("MD_")); - if (!DryRun) { Mat->PreEditChange(nullptr); Mat->MaterialDomain = NewDomain; Mat->PostEditChange(); } + if (!DryRun) Mat->MaterialDomain = NewDomain; } else if (Property == TEXT("blendMode")) { @@ -74,7 +74,7 @@ public: EBlendMode NewBlend; if (!MCPUtils::StringToEnum(ValueStr, NewBlend, Result, TEXT("BLEND_"))) return; NewValue = MCPUtils::EnumToString(NewBlend, TEXT("BLEND_")); - if (!DryRun) { Mat->PreEditChange(nullptr); Mat->BlendMode = NewBlend; Mat->PostEditChange(); } + if (!DryRun) Mat->BlendMode = NewBlend; } else if (Property == TEXT("shadingModel")) { @@ -83,14 +83,14 @@ public: EMaterialShadingModel NewModel; if (!MCPUtils::StringToEnum(ValueStr, NewModel, Result, TEXT("MSM_"))) return; NewValue = MCPUtils::EnumToString(NewModel, TEXT("MSM_")); - if (!DryRun) { Mat->PreEditChange(nullptr); Mat->SetShadingModel(NewModel); Mat->PostEditChange(); } + if (!DryRun) Mat->SetShadingModel(NewModel); } else if (Property == TEXT("opacity") || Property == TEXT("opacityMaskClipValue")) { double OpacityValue = Json->GetNumberField(TEXT("value")); OldValue = FString::Printf(TEXT("%g"), Mat->OpacityMaskClipValue); NewValue = FString::Printf(TEXT("%g"), OpacityValue); - if (!DryRun) { Mat->PreEditChange(nullptr); Mat->OpacityMaskClipValue = (float)OpacityValue; Mat->PostEditChange(); } + if (!DryRun) Mat->OpacityMaskClipValue = (float)OpacityValue; } else if (Property == TEXT("twoSided")) { @@ -124,9 +124,12 @@ public: return; } - // Save if not dry run + // Notify and save if not dry run if (!DryRun) { + TArray Chain = { Mat }; + MCPUtils::PreEdit(Chain); + MCPUtils::PostEdit(Chain); if (!MCPUtils::SaveMaterialPackage(Mat)) Result.Append(TEXT("WARNING: Package save failed\n")); } diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/StateMachine_SetTransitionRule.h b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/StateMachine_SetTransitionRule.h index 05831cf2..e05fc24d 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/StateMachine_SetTransitionRule.h +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers/StateMachine_SetTransitionRule.h @@ -74,9 +74,8 @@ public: return; } - // Update properties + // Count and apply property changes int32 ChangedCount = 0; - TransNode->PreEditChange(nullptr); if (Json->HasField(TEXT("crossfadeDuration"))) { @@ -109,7 +108,10 @@ public: Result.Append(TEXT("ERROR: No properties to update. Provide at least one of: crossfadeDuration, blendMode, priorityOrder, logicType, bBidirectional\n")); return; } - TransNode->PostEditChange(); + + TArray Chain = { TransNode }; + MCPUtils::PreEdit(Chain); + MCPUtils::PostEdit(Chain); // Compile and save FKismetEditorUtilities::CompileBlueprint(AnimBP); diff --git a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp index 40f30e06..d36dde2b 100644 --- a/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp +++ b/Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPUtils.cpp @@ -1132,7 +1132,7 @@ void MCPUtils::PostEdit(const TArray& Objects) Obj->MarkPackageDirty(); if (UBlueprint* BP = Cast(Obj)) - FBlueprintEditorUtils::MarkBlueprintAsModified(BP); + FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); } }