#include "MCPGraphLibrary.h" #include "Engine/Blueprint.h" #include "Engine/SimpleConstructionScript.h" #include "Engine/SCS_Node.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2_Actions.h" // K2Nodes #include "K2Node_CallFunction.h" #include "K2Node_Event.h" #include "K2Node_CustomEvent.h" #include "K2Node_ComponentBoundEvent.h" #include "K2Node_IfThenElse.h" #include "K2Node_ExecutionSequence.h" #include "K2Node_VariableGet.h" #include "K2Node_VariableSet.h" #include "K2Node_Self.h" #include "K2Node_DynamicCast.h" #include "K2Node_SwitchInteger.h" #include "K2Node_SwitchString.h" #include "K2Node_SwitchEnum.h" #include "K2Node_MakeArray.h" #include "K2Node_MakeStruct.h" #include "K2Node_BreakStruct.h" #include "K2Node_SetFieldsInStruct.h" #include "K2Node_MacroInstance.h" #include "K2Node_SpawnActorFromClass.h" #include "K2Node_FormatText.h" #include "K2Node_GetSubsystem.h" #include "K2Node_LoadAsset.h" #include "K2Node_AddDelegate.h" #include "K2Node_RemoveDelegate.h" #include "K2Node_CallDelegate.h" #include "K2Node_AssignDelegate.h" #include "K2Node_Knot.h" #include "K2Node_GetArrayItem.h" #include "K2Node_EnumLiteral.h" #include "K2Node_GetClassDefaults.h" #include "K2Node_FunctionResult.h" #include "K2Node_FunctionEntry.h" #include "K2Node_FunctionTerminator.h" #include "K2Node_Switch.h" #include "EdGraphNode_Comment.h" #include "Subsystems/AssetEditorSubsystem.h" #include "Editor.h" #include "BlueprintEditor.h" #include "AssetRegistry/AssetRegistryModule.h" #include "AssetRegistry/IAssetRegistry.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "WidgetBlueprint.h" #include "WidgetBlueprintEditorUtils.h" #include "Blueprint/WidgetTree.h" #include "Kismet/KismetSystemLibrary.h" #include "KismetCompilerMisc.h" #include "KismetCompiler.h" #include "Components/ActorComponent.h" #include "Components/BoxComponent.h" #include "Components/CanvasPanelSlot.h" #include "Components/NamedSlotInterface.h" #include "Components/PanelSlot.h" #include "Components/PanelWidget.h" #include "Components/SceneComponent.h" #include "Components/Widget.h" #include "Layout/Margin.h" #include "UObject/UnrealType.h" #include "Logging/TokenizedMessage.h" #include "Misc/UObjectToken.h" #include "Serialization/JsonSerializer.h" #include "Serialization/JsonWriter.h" #include "Dom/JsonObject.h" #include "Dom/JsonValue.h" // UMG WidgetAnimation authoring #include "Animation/WidgetAnimation.h" #include "MovieScene.h" #include "Tracks/MovieSceneFloatTrack.h" #include "Sections/MovieSceneFloatSection.h" #include "Channels/MovieSceneFloatChannel.h" #include "Channels/MovieSceneChannelProxy.h" // --------------------------------------------------------------------------- // Generic spawn helper // --------------------------------------------------------------------------- template TNode* UMCPGraphLibrary::PlaceNode(UEdGraph* Graph, FVector2D Position) { if (!Graph) return nullptr; TNode* Node = NewObject(Graph); Node->CreateNewGuid(); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, /*bUserAction*/ true, /*bSelectNewNode*/ false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); return Node; } static void MarkBPDirty(UBlueprint* BP) { if (BP) FBlueprintEditorUtils::MarkBlueprintAsModified(BP); } static FString MCPWidgetJsonStringify(const TSharedRef& Object) { FString Out; const TSharedRef> Writer = TJsonWriterFactory<>::Create(&Out); FJsonSerializer::Serialize(Object, Writer); return Out; } static UWidgetTree* MCPGetWidgetTree(UWidgetBlueprint* BP) { return BP ? BP->WidgetTree : nullptr; } static UWidget* MCPFindWidget(UWidgetBlueprint* BP, FName WidgetName) { UWidgetTree* Tree = MCPGetWidgetTree(BP); if (!Tree) return nullptr; if (WidgetName.IsNone() || WidgetName == FName(TEXT("Root")) || WidgetName == FName(TEXT("root"))) { return Tree->RootWidget; } return Tree->FindWidget(WidgetName); } static void MarkWidgetBPDirty(UWidgetBlueprint* BP, bool bStructurallyModified = true) { if (!BP) return; BP->Modify(); if (BP->WidgetTree) { BP->WidgetTree->SetFlags(RF_Transactional); BP->WidgetTree->Modify(); } if (bStructurallyModified) { FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BP); } else { FBlueprintEditorUtils::MarkBlueprintAsModified(BP); } } static bool SetObjectPropertyFromString(UObject* Object, FName PropertyName, const FString& Value) { if (!Object || PropertyName.IsNone()) return false; FProperty* Property = FindFProperty(Object->GetClass(), PropertyName); if (!Property) return false; Object->SetFlags(RF_Transactional); Object->Modify(); void* PropertyValue = Property->ContainerPtrToValuePtr(Object); const TCHAR* Buffer = *Value; if (!Property->ImportText_Direct(Buffer, PropertyValue, Object, PPF_None)) { return false; } #if WITH_EDITOR FPropertyChangedEvent Event(Property); Object->PostEditChangeProperty(Event); #endif return true; } static TSharedRef ExportObjectPropertiesJson(UObject* Object) { TSharedRef Root = MakeShared(); if (!Object) { Root->SetBoolField(TEXT("ok"), false); Root->SetStringField(TEXT("error"), TEXT("null object")); return Root; } Root->SetBoolField(TEXT("ok"), true); Root->SetStringField(TEXT("class"), Object->GetClass()->GetPathName()); TMap Props; FWidgetBlueprintEditorUtils::ExportPropertiesToText(Object, Props); TSharedRef PropJson = MakeShared(); for (const TPair& Pair : Props) { PropJson->SetStringField(Pair.Key.ToString(), Pair.Value); } Root->SetObjectField(TEXT("properties"), PropJson); return Root; } static TSharedRef WidgetSlotJson(UWidget* Widget) { TSharedRef SlotJson = MakeShared(); if (!Widget || !Widget->Slot) { SlotJson->SetBoolField(TEXT("has_slot"), false); return SlotJson; } UPanelSlot* Slot = Widget->Slot; SlotJson->SetBoolField(TEXT("has_slot"), true); SlotJson->SetStringField(TEXT("class"), Slot->GetClass()->GetPathName()); if (Slot->Parent) { SlotJson->SetStringField(TEXT("parent"), Slot->Parent->GetFName().ToString()); } if (UCanvasPanelSlot* CanvasSlot = Cast(Slot)) { const FVector2D Pos = CanvasSlot->GetPosition(); const FVector2D Size = CanvasSlot->GetSize(); const FVector2D Align = CanvasSlot->GetAlignment(); const FAnchors Anchors = CanvasSlot->GetAnchors(); SlotJson->SetArrayField(TEXT("position"), { MakeShared(Pos.X), MakeShared(Pos.Y) }); SlotJson->SetArrayField(TEXT("size"), { MakeShared(Size.X), MakeShared(Size.Y) }); SlotJson->SetArrayField(TEXT("alignment"), { MakeShared(Align.X), MakeShared(Align.Y) }); SlotJson->SetArrayField(TEXT("anchors_min"), { MakeShared(Anchors.Minimum.X), MakeShared(Anchors.Minimum.Y) }); SlotJson->SetArrayField(TEXT("anchors_max"), { MakeShared(Anchors.Maximum.X), MakeShared(Anchors.Maximum.Y) }); SlotJson->SetBoolField(TEXT("auto_size"), CanvasSlot->GetAutoSize()); SlotJson->SetNumberField(TEXT("z_order"), CanvasSlot->GetZOrder()); } return SlotJson; } static TSharedRef WidgetToJson(UWidget* Widget) { TSharedRef Json = MakeShared(); if (!Widget) { Json->SetBoolField(TEXT("ok"), false); return Json; } Json->SetStringField(TEXT("name"), Widget->GetFName().ToString()); Json->SetStringField(TEXT("class"), Widget->GetClass()->GetPathName()); Json->SetStringField(TEXT("label"), Widget->GetLabelText().ToString()); Json->SetBoolField(TEXT("is_variable"), Widget->bIsVariable); Json->SetObjectField(TEXT("slot"), WidgetSlotJson(Widget)); TArray> Children; if (UPanelWidget* Panel = Cast(Widget)) { const int32 ChildCount = Panel->GetChildrenCount(); for (int32 Index = 0; Index < ChildCount; ++Index) { if (UWidget* Child = Panel->GetChildAt(Index)) { TSharedRef ChildRef = MakeShared(); ChildRef->SetStringField(TEXT("name"), Child->GetFName().ToString()); ChildRef->SetStringField(TEXT("class"), Child->GetClass()->GetPathName()); ChildRef->SetNumberField(TEXT("index"), Index); Children.Add(MakeShared(ChildRef)); } } } Json->SetArrayField(TEXT("children"), Children); TArray SlotNames; if (INamedSlotInterface* NamedSlots = Cast(Widget)) { NamedSlots->GetSlotNames(SlotNames); TArray> NamedSlotValues; for (FName SlotName : SlotNames) { TSharedRef Slot = MakeShared(); Slot->SetStringField(TEXT("slot"), SlotName.ToString()); if (UWidget* Content = NamedSlots->GetContentForSlot(SlotName)) { Slot->SetStringField(TEXT("content"), Content->GetFName().ToString()); } NamedSlotValues.Add(MakeShared(Slot)); } Json->SetArrayField(TEXT("named_slots"), NamedSlotValues); } return Json; } static bool AddExistingWidgetToParent(UWidgetBlueprint* BP, UWidget* Widget, UWidget* Parent, int32 Index) { if (!BP || !Widget) return false; UWidgetTree* Tree = BP->WidgetTree; if (!Tree) return false; Widget->SetFlags(RF_Transactional); Widget->Modify(); if (!Parent) { if (Tree->RootWidget && Tree->RootWidget != Widget) { return false; } Tree->RootWidget = Widget; return true; } if (UPanelWidget* Panel = Cast(Parent)) { Panel->SetFlags(RF_Transactional); Panel->Modify(); if (!Panel->CanAddMoreChildren()) { return false; } if (Index >= 0 && Index <= Panel->GetChildrenCount()) { return Panel->InsertChildAt(Index, Widget) != nullptr; } return Panel->AddChild(Widget) != nullptr; } return false; } // --------------------------------------------------------------------------- // Graph lookup // --------------------------------------------------------------------------- UEdGraph* UMCPGraphLibrary::FindEventGraph(UBlueprint* Blueprint) { if (!Blueprint) return nullptr; for (UEdGraph* G : Blueprint->UbergraphPages) { if (G) return G; } return nullptr; } UEdGraph* UMCPGraphLibrary::FindFunctionGraph(UBlueprint* Blueprint, FName FunctionName) { if (!Blueprint) return nullptr; for (UEdGraph* G : Blueprint->FunctionGraphs) { if (G && G->GetFName() == FunctionName) return G; } for (UEdGraph* G : Blueprint->UbergraphPages) { if (G && G->GetFName() == FunctionName) return G; } for (UEdGraph* G : Blueprint->MacroGraphs) { if (G && G->GetFName() == FunctionName) return G; } return nullptr; } TArray UMCPGraphLibrary::ListNodeIds(UEdGraph* Graph) { TArray Out; if (!Graph) return Out; for (UEdGraphNode* N : Graph->Nodes) { if (N) Out.Add(N->NodeGuid.ToString()); } return Out; } TArray UMCPGraphLibrary::ListPinsOnNode(UEdGraph* Graph, const FString& NodeId) { TArray Out; UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) return Out; for (UEdGraphPin* P : N->Pins) { if (!P) continue; const TCHAR* Dir = (P->Direction == EGPD_Input) ? TEXT("in") : TEXT("out"); Out.Add(FString::Printf(TEXT("%s|%s|%s"), *P->PinName.ToString(), Dir, *P->PinType.PinCategory.ToString())); } return Out; } int32 UMCPGraphLibrary::ClearGraph(UEdGraph* Graph) { if (!Graph) return 0; TArray Snapshot = Graph->Nodes; int32 Deleted = 0; for (UEdGraphNode* N : Snapshot) { if (!N) continue; Graph->RemoveNode(N); ++Deleted; } if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) { MarkBPDirty(BP); } return Deleted; } bool UMCPGraphLibrary::DeleteNode(UEdGraph* Graph, const FString& NodeId) { if (!Graph) return false; UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) return false; Graph->RemoveNode(N); if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP); return true; } // --------------------------------------------------------------------------- // Spawners — basic // --------------------------------------------------------------------------- FString UMCPGraphLibrary::SpawnCallFunctionNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* FunctionOwnerClass, FName FunctionName, FVector2D Position) { if (!Blueprint || !Graph || !FunctionOwnerClass) return FString(); UK2Node_CallFunction* Node = NewObject(Graph); Node->CreateNewGuid(); Node->FunctionReference.SetExternalMember(FunctionName, FunctionOwnerClass); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnEventNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* OwnerClass, FName EventName, FVector2D Position) { if (!Blueprint || !Graph) return FString(); UClass* EffectiveOwner = OwnerClass; if (!EffectiveOwner) EffectiveOwner = Blueprint->ParentClass; if (!EffectiveOwner) return FString(); UK2Node_Event* Node = NewObject(Graph); Node->CreateNewGuid(); Node->EventReference.SetExternalMember(EventName, EffectiveOwner); Node->bOverrideFunction = true; Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnComponentBoundEvent(UBlueprint* Blueprint, UEdGraph* Graph, FName ComponentName, FName DelegateName, FVector2D Position) { if (!Blueprint || !Graph) return FString(); UClass* OwnerClass = Blueprint->GeneratedClass ? Blueprint->GeneratedClass : Blueprint->ParentClass; if (!OwnerClass) return FString(); FObjectProperty* CompProp = FindFProperty(OwnerClass, ComponentName); if (!CompProp || !CompProp->PropertyClass) return FString(); FMulticastDelegateProperty* DelegateProp = FindFProperty(CompProp->PropertyClass, DelegateName); if (!DelegateProp) return FString(); UK2Node_ComponentBoundEvent* Node = NewObject(Graph); Node->CreateNewGuid(); Node->InitializeComponentBoundEventParams(CompProp, DelegateProp); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnCustomEventNode(UBlueprint* Blueprint, UEdGraph* Graph, FName EventName, FVector2D Position) { UK2Node_CustomEvent* Node = PlaceNode(Graph, Position); if (!Node) return FString(); Node->CustomFunctionName = EventName; Node->ReconstructNode(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } bool UMCPGraphLibrary::SetCustomEventNetworkFlags(UBlueprint* Blueprint, UEdGraph* Graph, const FString& NodeIdOrEventName, FName Mode, bool bReliable) { if (!Graph) return false; UK2Node_CustomEvent* EventNode = Cast(FindNodeByGuid(Graph, NodeIdOrEventName)); if (!EventNode) { for (UEdGraphNode* Node : Graph->Nodes) { UK2Node_CustomEvent* Candidate = Cast(Node); if (Candidate && Candidate->CustomFunctionName.ToString() == NodeIdOrEventName) { EventNode = Candidate; break; } } } if (!EventNode) return false; uint32 Flags = EventNode->FunctionFlags; Flags &= ~(FUNC_Net | FUNC_NetServer | FUNC_NetClient | FUNC_NetMulticast | FUNC_NetReliable); const FString ModeText = Mode.ToString().ToLower(); if (ModeText == TEXT("server") || ModeText == TEXT("run_on_server")) { Flags |= FUNC_Net | FUNC_NetServer; } else if (ModeText == TEXT("client") || ModeText == TEXT("run_on_owning_client")) { Flags |= FUNC_Net | FUNC_NetClient; } else if (ModeText == TEXT("multicast") || ModeText == TEXT("netmulticast")) { Flags |= FUNC_Net | FUNC_NetMulticast; } else if (ModeText == TEXT("none") || ModeText == TEXT("local")) { // Keep flags cleared. } else { return false; } if (bReliable && (Flags & FUNC_Net)) { Flags |= FUNC_NetReliable; } EventNode->Modify(); EventNode->FunctionFlags = Flags; EventNode->ReconstructNode(); MarkBPDirty(Blueprint ? Blueprint : FBlueprintEditorUtils::FindBlueprintForGraph(Graph)); return true; } FString UMCPGraphLibrary::SpawnBranchNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_IfThenElse* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnSequenceNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position, int32 OutputCount) { UK2Node_ExecutionSequence* Node = PlaceNode(Graph, Position); if (!Node) return FString(); const int32 Extra = FMath::Max(0, OutputCount - 2); for (int32 i = 0; i < Extra; ++i) Node->AddInputPin(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnVariableGetNode(UBlueprint* Blueprint, UEdGraph* Graph, FName VariableName, FVector2D Position) { if (!Blueprint || !Graph) return FString(); UK2Node_VariableGet* Node = NewObject(Graph); Node->CreateNewGuid(); Node->VariableReference.SetSelfMember(VariableName); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnVariableSetNode(UBlueprint* Blueprint, UEdGraph* Graph, FName VariableName, FVector2D Position) { if (!Blueprint || !Graph) return FString(); UK2Node_VariableSet* Node = NewObject(Graph); Node->CreateNewGuid(); Node->VariableReference.SetSelfMember(VariableName); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnSelfNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_Self* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnCastNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* TargetClass, FVector2D Position) { if (!Blueprint || !Graph || !TargetClass) return FString(); UK2Node_DynamicCast* Node = NewObject(Graph); Node->CreateNewGuid(); Node->TargetType = TargetClass; Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } // --------------------------------------------------------------------------- // Spawners — flow / collections // --------------------------------------------------------------------------- FString UMCPGraphLibrary::SpawnSwitchOnIntNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_SwitchInteger* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnSwitchOnStringNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_SwitchString* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnSwitchOnEnumNode(UBlueprint* Blueprint, UEdGraph* Graph, UEnum* EnumType, FVector2D Position) { if (!Blueprint || !Graph || !EnumType) return FString(); UK2Node_SwitchEnum* Node = NewObject(Graph); Node->CreateNewGuid(); Node->SetEnum(EnumType); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnMakeArrayNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_MakeArray* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnMakeStructNode(UBlueprint* Blueprint, UEdGraph* Graph, UScriptStruct* StructType, FVector2D Position) { if (!Blueprint || !Graph || !StructType) return FString(); UK2Node_MakeStruct* Node = NewObject(Graph); Node->CreateNewGuid(); Node->StructType = StructType; Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnBreakStructNode(UBlueprint* Blueprint, UEdGraph* Graph, UScriptStruct* StructType, FVector2D Position) { if (!Blueprint || !Graph || !StructType) return FString(); UK2Node_BreakStruct* Node = NewObject(Graph); Node->CreateNewGuid(); Node->StructType = StructType; Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnSetFieldsInStructNode(UBlueprint* Blueprint, UEdGraph* Graph, UScriptStruct* StructType, FVector2D Position) { if (!Blueprint || !Graph || !StructType) return FString(); UK2Node_SetFieldsInStruct* Node = NewObject(Graph); Node->CreateNewGuid(); Node->StructType = StructType; Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnStandardMacroNode(UBlueprint* Blueprint, UEdGraph* Graph, FName MacroName, FVector2D Position) { if (!Blueprint || !Graph) return FString(); // Load StandardMacros library (engine asset). UBlueprint* MacroLib = LoadObject(nullptr, TEXT("/Engine/EditorBlueprintResources/StandardMacros.StandardMacros")); if (!MacroLib) return FString(); UEdGraph* MacroGraph = nullptr; for (UEdGraph* G : MacroLib->MacroGraphs) { if (G && G->GetFName() == MacroName) { MacroGraph = G; break; } } if (!MacroGraph) return FString(); UK2Node_MacroInstance* Node = NewObject(Graph); Node->CreateNewGuid(); Node->SetMacroGraph(MacroGraph); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnSpawnActorFromClassNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_SpawnActorFromClass* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnFormatTextNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_FormatText* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnGetSubsystemNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* SubsystemClass, FVector2D Position) { if (!Blueprint || !Graph || !SubsystemClass) return FString(); UK2Node_GetSubsystem* Node = NewObject(Graph); Node->CreateNewGuid(); Node->Initialize(SubsystemClass); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnLoadAssetNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_LoadAsset* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } // --------------------------------------------------------------------------- // Delegates // --------------------------------------------------------------------------- static bool ResolveComponentDelegate(UBlueprint* BP, FName ComponentName, FName DelegateName, FObjectProperty*& OutCompProp, FMulticastDelegateProperty*& OutDelegateProp) { OutCompProp = nullptr; OutDelegateProp = nullptr; if (!BP) return false; UClass* OwnerClass = BP->GeneratedClass ? BP->GeneratedClass : BP->ParentClass; if (!OwnerClass) return false; OutCompProp = FindFProperty(OwnerClass, ComponentName); if (!OutCompProp || !OutCompProp->PropertyClass) return false; OutDelegateProp = FindFProperty(OutCompProp->PropertyClass, DelegateName); return OutDelegateProp != nullptr; } FString UMCPGraphLibrary::SpawnBindDelegateNode(UBlueprint* Blueprint, UEdGraph* Graph, FName ComponentName, FName DelegateName, FVector2D Position) { FObjectProperty* CompProp = nullptr; FMulticastDelegateProperty* DelegateProp = nullptr; if (!ResolveComponentDelegate(Blueprint, ComponentName, DelegateName, CompProp, DelegateProp)) return FString(); UK2Node_AddDelegate* Node = NewObject(Graph); Node->CreateNewGuid(); Node->SetFromProperty(DelegateProp, /*bSelfContext*/ false, DelegateProp->GetOwnerClass()); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnUnbindDelegateNode(UBlueprint* Blueprint, UEdGraph* Graph, FName ComponentName, FName DelegateName, FVector2D Position) { FObjectProperty* CompProp = nullptr; FMulticastDelegateProperty* DelegateProp = nullptr; if (!ResolveComponentDelegate(Blueprint, ComponentName, DelegateName, CompProp, DelegateProp)) return FString(); UK2Node_RemoveDelegate* Node = NewObject(Graph); Node->CreateNewGuid(); Node->SetFromProperty(DelegateProp, false, DelegateProp->GetOwnerClass()); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnCallDelegateNode(UBlueprint* Blueprint, UEdGraph* Graph, FName DispatcherName, FVector2D Position) { if (!Blueprint || !Graph) return FString(); UClass* OwnerClass = Blueprint->GeneratedClass ? Blueprint->GeneratedClass : Blueprint->ParentClass; if (!OwnerClass) return FString(); FMulticastDelegateProperty* DelegateProp = FindFProperty(OwnerClass, DispatcherName); if (!DelegateProp) return FString(); UK2Node_CallDelegate* Node = NewObject(Graph); Node->CreateNewGuid(); Node->SetFromProperty(DelegateProp, /*bSelfContext*/ true, OwnerClass); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnAssignDelegateNode(UBlueprint* Blueprint, UEdGraph* Graph, FName ComponentName, FName DelegateName, FVector2D Position) { FObjectProperty* CompProp = nullptr; FMulticastDelegateProperty* DelegateProp = nullptr; if (!ResolveComponentDelegate(Blueprint, ComponentName, DelegateName, CompProp, DelegateProp)) return FString(); UK2Node_AssignDelegate* Node = NewObject(Graph); Node->CreateNewGuid(); Node->SetFromProperty(DelegateProp, false, DelegateProp->GetOwnerClass()); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } bool UMCPGraphLibrary::AddEventDispatcher(UBlueprint* Blueprint, FName DispatcherName) { if (!Blueprint || DispatcherName.IsNone()) return false; // Create the signature graph (void signature; user can add params via UI). UEdGraph* SignatureGraph = FBlueprintEditorUtils::CreateNewGraph( Blueprint, DispatcherName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); if (!SignatureGraph) return false; SignatureGraph->bEditable = false; SignatureGraph->bAllowDeletion = true; Blueprint->DelegateSignatureGraphs.Add(SignatureGraph); const UEdGraphSchema_K2* Schema = GetDefault(); Schema->CreateDefaultNodesForGraph(*SignatureGraph); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); return true; } // --------------------------------------------------------------------------- // Edges & defaults // --------------------------------------------------------------------------- UEdGraphNode* UMCPGraphLibrary::FindNodeByGuid(UEdGraph* Graph, const FString& NodeId) { if (!Graph) return nullptr; FGuid Guid; if (!FGuid::Parse(NodeId, Guid)) return nullptr; for (UEdGraphNode* N : Graph->Nodes) { if (N && N->NodeGuid == Guid) return N; } return nullptr; } UEdGraphPin* UMCPGraphLibrary::FindPinByName(UEdGraphNode* Node, const FName& PinName) { if (!Node) return nullptr; return Node->FindPin(PinName); } bool UMCPGraphLibrary::ConnectPins(UEdGraph* Graph, const FString& FromNodeId, FName FromPin, const FString& ToNodeId, FName ToPin) { if (!Graph) return false; UEdGraphNode* A = FindNodeByGuid(Graph, FromNodeId); UEdGraphNode* B = FindNodeByGuid(Graph, ToNodeId); if (!A || !B) return false; UEdGraphPin* PA = FindPinByName(A, FromPin); UEdGraphPin* PB = FindPinByName(B, ToPin); if (!PA || !PB) return false; const UEdGraphSchema* Schema = Graph->GetSchema(); if (!Schema) return false; return Schema->TryCreateConnection(PA, PB); } bool UMCPGraphLibrary::BreakLink(UEdGraph* Graph, const FString& FromNodeId, FName FromPin, const FString& ToNodeId, FName ToPin) { if (!Graph) return false; UEdGraphNode* A = FindNodeByGuid(Graph, FromNodeId); UEdGraphNode* B = FindNodeByGuid(Graph, ToNodeId); if (!A || !B) return false; UEdGraphPin* PA = FindPinByName(A, FromPin); UEdGraphPin* PB = FindPinByName(B, ToPin); if (!PA || !PB) return false; PA->BreakLinkTo(PB); return true; } bool UMCPGraphLibrary::BreakAllNodeLinks(UEdGraph* Graph, const FString& NodeId) { UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) return false; N->BreakAllNodeLinks(); return true; } bool UMCPGraphLibrary::SetPinDefault(UEdGraph* Graph, const FString& NodeId, FName PinName, const FString& Value) { if (!Graph) return false; UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) return false; UEdGraphPin* P = FindPinByName(N, PinName); if (!P) return false; const UEdGraphSchema* Schema = Graph->GetSchema(); if (!Schema) return false; Schema->TrySetDefaultValue(*P, Value); return true; } // --------------------------------------------------------------------------- // BP-level authoring // --------------------------------------------------------------------------- UEdGraph* UMCPGraphLibrary::AddFunctionGraph(UBlueprint* Blueprint, FName FunctionName) { if (!Blueprint || FunctionName.IsNone()) return nullptr; UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph( Blueprint, FunctionName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); if (!NewGraph) return nullptr; FBlueprintEditorUtils::AddFunctionGraph(Blueprint, NewGraph, /*bIsUserCreated*/ true, (UClass*)nullptr); return NewGraph; } UEdGraph* UMCPGraphLibrary::AddMacroGraph(UBlueprint* Blueprint, FName MacroName) { if (!Blueprint || MacroName.IsNone()) return nullptr; UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph( Blueprint, MacroName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass()); if (!NewGraph) return nullptr; FBlueprintEditorUtils::AddMacroGraph(Blueprint, NewGraph, /*bIsUserCreated*/ true, (UClass*)nullptr); return NewGraph; } bool UMCPGraphLibrary::AddInterface(UBlueprint* Blueprint, const FString& InterfacePath) { if (!Blueprint || InterfacePath.IsEmpty()) return false; return FBlueprintEditorUtils::ImplementNewInterface(Blueprint, FTopLevelAssetPath(InterfacePath)); } bool UMCPGraphLibrary::SetVariableDefaultValue(UBlueprint* Blueprint, FName VariableName, const FString& Value) { if (!Blueprint || VariableName.IsNone()) return false; for (FBPVariableDescription& Var : Blueprint->NewVariables) { if (Var.VarName == VariableName) { Var.DefaultValue = Value; MarkBPDirty(Blueprint); return true; } } return false; } bool UMCPGraphLibrary::SetVariableMetadata(UBlueprint* Blueprint, FName VariableName, bool bInstanceEditable, bool bExposeOnSpawn, const FString& Category, const FString& Tooltip) { if (!Blueprint || VariableName.IsNone()) return false; // FBlueprintEditorUtils doesn't expose dedicated InstanceEditable/ExposeOnSpawn // setters as standalone fns in 5.7. Manipulate FBPVariableDescription directly. bool bFound = false; for (FBPVariableDescription& Var : Blueprint->NewVariables) { if (Var.VarName != VariableName) continue; bFound = true; if (bInstanceEditable) Var.PropertyFlags &= ~CPF_DisableEditOnInstance; else Var.PropertyFlags |= CPF_DisableEditOnInstance; // NOTE: UE treats the mere PRESENCE of the "ExposeOnSpawn" metadata key as // "exposed" (the value "false" does NOT un-expose it). Remove the key when // disabling, only add it when enabling. if (bExposeOnSpawn) { Var.SetMetaData(TEXT("ExposeOnSpawn"), TEXT("true")); Var.PropertyFlags |= CPF_ExposeOnSpawn; } else { Var.RemoveMetaData(TEXT("ExposeOnSpawn")); Var.PropertyFlags &= ~CPF_ExposeOnSpawn; } break; } if (!bFound) return false; if (!Category.IsEmpty()) { FBlueprintEditorUtils::SetBlueprintVariableCategory(Blueprint, VariableName, nullptr, FText::FromString(Category), false); } if (!Tooltip.IsEmpty()) { FBlueprintEditorUtils::SetBlueprintVariableMetaData(Blueprint, VariableName, nullptr, TEXT("Tooltip"), Tooltip); } MarkBPDirty(Blueprint); return true; } bool UMCPGraphLibrary::AddLocalVariable(UBlueprint* Blueprint, FName FunctionName, FName VariableName, const FString& TypeSpec, const FString& DefaultValue) { if (!Blueprint || FunctionName.IsNone() || VariableName.IsNone()) return false; UEdGraph* FuncGraph = FindFunctionGraph(Blueprint, FunctionName); if (!FuncGraph) return false; FEdGraphPinType PinType; const FString T = TypeSpec.ToLower(); if (T == TEXT("bool")) PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean; else if (T == TEXT("int")) PinType.PinCategory = UEdGraphSchema_K2::PC_Int; else if (T == TEXT("float")) { PinType.PinCategory = UEdGraphSchema_K2::PC_Real; PinType.PinSubCategory = UEdGraphSchema_K2::PC_Float; } else if (T == TEXT("string")) PinType.PinCategory = UEdGraphSchema_K2::PC_String; else if (T == TEXT("name")) PinType.PinCategory = UEdGraphSchema_K2::PC_Name; else if (T == TEXT("text")) PinType.PinCategory = UEdGraphSchema_K2::PC_Text; else { UClass* Cls = TypeSpec.Contains(TEXT("/")) ? LoadObject(nullptr, *TypeSpec) : FindFirstObject(*TypeSpec, EFindFirstObjectOptions::None); if (!Cls) return false; PinType.PinCategory = UEdGraphSchema_K2::PC_Object; PinType.PinSubCategoryObject = Cls; } return FBlueprintEditorUtils::AddLocalVariable(Blueprint, FuncGraph, VariableName, PinType, DefaultValue); } FString UMCPGraphLibrary::AddComponentToBlueprint(UBlueprint* Blueprint, UClass* ComponentClass, FName ComponentName) { if (!Blueprint || !ComponentClass) return FString(); if (!ComponentClass->IsChildOf(UActorComponent::StaticClass())) return FString(); USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; if (!SCS) return FString(); for (USCS_Node* Existing : SCS->GetAllNodes()) { if (Existing && Existing->GetVariableName() == ComponentName) return Existing->GetVariableName().ToString(); } USCS_Node* NewNode = SCS->CreateNode(ComponentClass, ComponentName); if (!NewNode) return FString(); SCS->AddNode(NewNode); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); return NewNode->GetVariableName().ToString(); } bool UMCPGraphLibrary::AddMemberVariable(UBlueprint* Blueprint, FName VariableName, const FString& TypeSpec) { if (!Blueprint || VariableName.IsNone() || TypeSpec.IsEmpty()) return false; FEdGraphPinType PinType; const FString T = TypeSpec.ToLower(); if (T == TEXT("bool")) PinType.PinCategory = UEdGraphSchema_K2::PC_Boolean; else if (T == TEXT("int")) PinType.PinCategory = UEdGraphSchema_K2::PC_Int; else if (T == TEXT("float")) { PinType.PinCategory = UEdGraphSchema_K2::PC_Real; PinType.PinSubCategory = UEdGraphSchema_K2::PC_Float; } else if (T == TEXT("double")) { PinType.PinCategory = UEdGraphSchema_K2::PC_Real; PinType.PinSubCategory = UEdGraphSchema_K2::PC_Double; } else if (T == TEXT("string")) PinType.PinCategory = UEdGraphSchema_K2::PC_String; else if (T == TEXT("name")) PinType.PinCategory = UEdGraphSchema_K2::PC_Name; else if (T == TEXT("text")) PinType.PinCategory = UEdGraphSchema_K2::PC_Text; else { UClass* Cls = TypeSpec.Contains(TEXT("/")) ? LoadObject(nullptr, *TypeSpec) : FindFirstObject(*TypeSpec, EFindFirstObjectOptions::None); if (!Cls) return false; PinType.PinCategory = UEdGraphSchema_K2::PC_Object; PinType.PinSubCategoryObject = Cls; } return FBlueprintEditorUtils::AddMemberVariable(Blueprint, VariableName, PinType); } USCS_Node* UMCPGraphLibrary::FindSCSNode(UBlueprint* Blueprint, FName ComponentName) { if (!Blueprint || !Blueprint->SimpleConstructionScript) return nullptr; for (USCS_Node* N : Blueprint->SimpleConstructionScript->GetAllNodes()) { if (N && N->GetVariableName() == ComponentName) return N; } return nullptr; } bool UMCPGraphLibrary::RemoveComponent(UBlueprint* Blueprint, FName ComponentName) { USCS_Node* N = FindSCSNode(Blueprint, ComponentName); if (!N) return false; Blueprint->SimpleConstructionScript->RemoveNode(N); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); return true; } bool UMCPGraphLibrary::AttachComponent(UBlueprint* Blueprint, FName ChildName, FName ParentName, FName SocketName) { USCS_Node* Child = FindSCSNode(Blueprint, ChildName); USCS_Node* Parent = FindSCSNode(Blueprint, ParentName); if (!Child || !Parent) return false; USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; // Detach from current parent for (USCS_Node* N : SCS->GetAllNodes()) { if (N) N->RemoveChildNode(Child, /*bRecursive*/ false); } SCS->RemoveNode(Child); // Re-add and attach Parent->AddChildNode(Child); if (!SocketName.IsNone()) Child->AttachToName = SocketName; FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); return true; } bool UMCPGraphLibrary::SetRootComponent(UBlueprint* Blueprint, FName ComponentName) { USCS_Node* Target = FindSCSNode(Blueprint, ComponentName); if (!Target) return false; USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript; // Detach from any existing parent first, then re-add as a top-level root. // Note: SCS::RootNodes is private and has no public reorder API in 5.7, so // the node becomes "a" root, not necessarily the FIRST root. For most BPs // the existing root is a DefaultSceneRoot which the SCS auto-replaces when // a new scene-root candidate appears. for (USCS_Node* N : SCS->GetAllNodes()) { if (N) N->RemoveChildNode(Target, /*bRecursive*/ false); } SCS->RemoveNode(Target); SCS->AddNode(Target); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); return true; } bool UMCPGraphLibrary::SetComponentProperty(UBlueprint* Blueprint, FName ComponentName, FName PropertyName, const FString& Value) { USCS_Node* N = FindSCSNode(Blueprint, ComponentName); if (!N || !N->ComponentTemplate) return false; UActorComponent* Tmpl = N->ComponentTemplate; FProperty* Prop = FindFProperty(Tmpl->GetClass(), PropertyName); if (!Prop) return false; void* ValuePtr = Prop->ContainerPtrToValuePtr(Tmpl); if (!ValuePtr) return false; Prop->ImportText_Direct(*Value, ValuePtr, Tmpl, PPF_None); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); return true; } bool UMCPGraphLibrary::SetComponentTransform(UBlueprint* Blueprint, FName ComponentName, FVector Location, FRotator Rotation, FVector Scale) { USCS_Node* N = FindSCSNode(Blueprint, ComponentName); if (!N) return false; USceneComponent* Scene = Cast(N->ComponentTemplate); if (!Scene) return false; Scene->SetRelativeLocation(Location); Scene->SetRelativeRotation(Rotation); Scene->SetRelativeScale3D(Scale); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); return true; } bool UMCPGraphLibrary::ConfigureBoxComponent(UBlueprint* Blueprint, FName ComponentName, FVector Extent, FName CollisionProfile) { USCS_Node* N = FindSCSNode(Blueprint, ComponentName); if (!N) return false; UBoxComponent* Box = Cast(N->ComponentTemplate); if (!Box) return false; Box->SetBoxExtent(Extent, false); if (!CollisionProfile.IsNone()) Box->SetCollisionProfileName(CollisionProfile); Box->SetGenerateOverlapEvents(true); FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint); return true; } // --------------------------------------------------------------------------- // Compile / introspect // --------------------------------------------------------------------------- bool UMCPGraphLibrary::CompileAndSaveBlueprint(UBlueprint* Blueprint) { if (!Blueprint) return false; FKismetEditorUtilities::CompileBlueprint(Blueprint); return true; } TArray UMCPGraphLibrary::GetCompileErrors(UBlueprint* Blueprint) { TArray Out; if (!Blueprint) return Out; // BP keeps status — return high-level state for now. Detailed messages // require capturing FCompilerResultsLog during compile, which we do not // wire up here; this still gives a quick health signal. switch (Blueprint->Status) { case BS_UpToDate: Out.Add(TEXT("UpToDate")); break; case BS_Dirty: Out.Add(TEXT("Dirty")); break; case BS_Error: Out.Add(TEXT("Error")); break; case BS_UpToDateWithWarnings: Out.Add(TEXT("UpToDateWithWarnings")); break; case BS_Unknown: Out.Add(TEXT("Unknown")); break; case BS_BeingCreated: Out.Add(TEXT("BeingCreated")); break; default: Out.Add(TEXT("Other")); break; } return Out; } TArray UMCPGraphLibrary::ListFunctionsOnClass(UClass* Class) { TArray Out; if (!Class) return Out; for (TFieldIterator It(Class); It; ++It) { UFunction* F = *It; if (!F) continue; Out.Add(F->GetName()); } return Out; } TArray UMCPGraphLibrary::ListPropertiesOnClass(UClass* Class) { TArray Out; if (!Class) return Out; for (TFieldIterator It(Class); It; ++It) { FProperty* P = *It; if (!P) continue; Out.Add(FString::Printf(TEXT("%s|%s"), *P->GetName(), *P->GetCPPType())); } return Out; } TArray UMCPGraphLibrary::ListDelegatesOnClass(UClass* Class) { TArray Out; if (!Class) return Out; for (TFieldIterator It(Class); It; ++It) { FMulticastDelegateProperty* D = *It; if (!D) continue; Out.Add(D->GetName()); } return Out; } FString UMCPGraphLibrary::GetPinType(UClass* OwnerClass, FName FunctionName, FName PinName) { if (!OwnerClass) return FString(); UFunction* Fn = OwnerClass->FindFunctionByName(FunctionName); if (!Fn) return FString(); FProperty* Param = Fn->FindPropertyByName(PinName); if (!Param) return FString(); return Param->GetCPPType(); } // =========================================================================== // Tier 2 spawners // =========================================================================== FString UMCPGraphLibrary::SpawnKnotNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_Knot* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnCommentNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position, FVector2D Size, const FString& Comment) { if (!Graph) return FString(); UEdGraphNode_Comment* Node = NewObject(Graph); Node->CreateNewGuid(); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Node->NodeWidth = FMath::Max(64, static_cast(Size.X)); Node->NodeHeight = FMath::Max(64, static_cast(Size.Y)); Node->NodeComment = Comment; Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnEnumLiteralNode(UBlueprint* Blueprint, UEdGraph* Graph, UEnum* EnumType, FVector2D Position) { if (!Graph || !EnumType) return FString(); UK2Node_EnumLiteral* Node = NewObject(Graph); Node->CreateNewGuid(); Node->Enum = EnumType; Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnGetClassDefaultsNode(UBlueprint* Blueprint, UEdGraph* Graph, UClass* TargetClass, FVector2D Position) { if (!Graph || !TargetClass) return FString(); UK2Node_GetClassDefaults* Node = NewObject(Graph); Node->CreateNewGuid(); Node->NodePosX = static_cast(Position.X); Node->NodePosY = static_cast(Position.Y); Graph->AddNode(Node, true, false); Node->PostPlacedNewNode(); Node->AllocateDefaultPins(); // Set the class on the input pin if (UEdGraphPin* ClassPin = Node->FindPin(TEXT("Class"))) { Graph->GetSchema()->TrySetDefaultObject(*ClassPin, TargetClass); Node->ReconstructNode(); } MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnFunctionResultNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_FunctionResult* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnGetArrayItemNode(UBlueprint* Blueprint, UEdGraph* Graph, FVector2D Position) { UK2Node_GetArrayItem* Node = PlaceNode(Graph, Position); if (!Node) return FString(); MarkBPDirty(Blueprint); return Node->NodeGuid.ToString(); } FString UMCPGraphLibrary::SpawnPromotableOperatorNode(UBlueprint* Blueprint, UEdGraph* Graph, FName OperatorName, FVector2D Position) { // Promotable ops in UE are really KismetMathLibrary call_function nodes (Add_IntInt, // Subtract_FloatFloat, etc.) that the editor auto-promotes. Easiest portable impl: // route to KismetMathLibrary by symbolic operator -> wildcard function name. if (!Blueprint || !Graph) return FString(); static const TMap OpMap = { { TEXT("+"), TEXT("Add_IntInt") }, { TEXT("-"), TEXT("Subtract_IntInt") }, { TEXT("*"), TEXT("Multiply_IntInt") }, { TEXT("/"), TEXT("Divide_IntInt") }, { TEXT("=="), TEXT("EqualEqual_IntInt") }, { TEXT("!="), TEXT("NotEqual_IntInt") }, { TEXT("<"), TEXT("Less_IntInt") }, { TEXT(">"), TEXT("Greater_IntInt") }, { TEXT("<="), TEXT("LessEqual_IntInt") }, { TEXT(">="), TEXT("GreaterEqual_IntInt") }, }; const FName* Mapped = OpMap.Find(OperatorName); if (!Mapped) return FString(); UClass* KML = LoadClass(nullptr, TEXT("/Script/Engine.KismetMathLibrary")); if (!KML) return FString(); return SpawnCallFunctionNode(Blueprint, Graph, KML, *Mapped, Position); } bool UMCPGraphLibrary::AddCasePinToSwitch(UEdGraph* Graph, const FString& NodeId) { UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) return false; UK2Node_Switch* Sw = Cast(N); if (!Sw) return false; Sw->AddPinToSwitchNode(); Sw->ReconstructNode(); if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP); return true; } bool UMCPGraphLibrary::FormatTextSetFormat(UEdGraph* Graph, const FString& NodeId, const FString& Format) { UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) return false; UK2Node_FormatText* FT = Cast(N); if (!FT) return false; UEdGraphPin* FormatPin = FT->FindPin(TEXT("Format")); if (!FormatPin) return false; Graph->GetSchema()->TrySetDefaultText(*FormatPin, FText::FromString(Format)); FT->PinDefaultValueChanged(FormatPin); FT->ReconstructNode(); if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP); return true; } bool UMCPGraphLibrary::ReconstructNode(UEdGraph* Graph, const FString& NodeId) { UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) return false; N->ReconstructNode(); if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP); return true; } // =========================================================================== // Authoring — rename / delete / function params // =========================================================================== bool UMCPGraphLibrary::RenameVariable(UBlueprint* Blueprint, FName OldName, FName NewName) { if (!Blueprint || OldName.IsNone() || NewName.IsNone()) return false; FBlueprintEditorUtils::RenameMemberVariable(Blueprint, OldName, NewName); return true; } bool UMCPGraphLibrary::DeleteVariable(UBlueprint* Blueprint, FName VariableName) { if (!Blueprint || VariableName.IsNone()) return false; FBlueprintEditorUtils::RemoveMemberVariable(Blueprint, VariableName); return true; } bool UMCPGraphLibrary::RenameFunction(UBlueprint* Blueprint, FName OldName, FName NewName) { if (!Blueprint || OldName.IsNone() || NewName.IsNone()) return false; UEdGraph* G = FindFunctionGraph(Blueprint, OldName); if (!G) return false; FBlueprintEditorUtils::RenameGraph(G, NewName.ToString()); return true; } bool UMCPGraphLibrary::RemoveFunctionGraph(UBlueprint* Blueprint, FName FunctionName) { if (!Blueprint || FunctionName.IsNone()) return false; UEdGraph* G = nullptr; for (UEdGraph* Fg : Blueprint->FunctionGraphs) { if (Fg && Fg->GetFName() == FunctionName) { G = Fg; break; } } if (!G) return false; FBlueprintEditorUtils::RemoveGraph(Blueprint, G, EGraphRemoveFlags::Recompile); return true; } bool UMCPGraphLibrary::RemoveMacroGraph(UBlueprint* Blueprint, FName MacroName) { if (!Blueprint || MacroName.IsNone()) return false; UEdGraph* G = nullptr; for (UEdGraph* Mg : Blueprint->MacroGraphs) { if (Mg && Mg->GetFName() == MacroName) { G = Mg; break; } } if (!G) return false; FBlueprintEditorUtils::RemoveGraph(Blueprint, G, EGraphRemoveFlags::Recompile); return true; } static bool ParsePinTypeFromSpec(const FString& TypeSpec, FEdGraphPinType& OutPinType) { const FString T = TypeSpec.ToLower(); if (T == TEXT("bool")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_Boolean; else if (T == TEXT("int")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_Int; else if (T == TEXT("float")) { OutPinType.PinCategory = UEdGraphSchema_K2::PC_Real; OutPinType.PinSubCategory = UEdGraphSchema_K2::PC_Float; } else if (T == TEXT("double")) { OutPinType.PinCategory = UEdGraphSchema_K2::PC_Real; OutPinType.PinSubCategory = UEdGraphSchema_K2::PC_Double; } else if (T == TEXT("string")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_String; else if (T == TEXT("name")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_Name; else if (T == TEXT("text")) OutPinType.PinCategory = UEdGraphSchema_K2::PC_Text; else { UClass* Cls = TypeSpec.Contains(TEXT("/")) ? LoadObject(nullptr, *TypeSpec) : FindFirstObject(*TypeSpec, EFindFirstObjectOptions::None); if (!Cls) return false; OutPinType.PinCategory = UEdGraphSchema_K2::PC_Object; OutPinType.PinSubCategoryObject = Cls; } return true; } bool UMCPGraphLibrary::AddFunctionParameter(UBlueprint* Blueprint, FName FunctionName, FName ParamName, const FString& TypeSpec, const FString& Direction) { if (!Blueprint || FunctionName.IsNone() || ParamName.IsNone()) return false; UEdGraph* G = FindFunctionGraph(Blueprint, FunctionName); if (!G) return false; FEdGraphPinType PinType; if (!ParsePinTypeFromSpec(TypeSpec, PinType)) return false; const bool bInput = Direction.IsEmpty() || Direction.Equals(TEXT("input"), ESearchCase::IgnoreCase); // Inputs go on the EntryNode; outputs on the ResultNode (created if missing). UK2Node_EditablePinBase* TargetNode = nullptr; for (UEdGraphNode* N : G->Nodes) { if (bInput && N->IsA()) { TargetNode = Cast(N); break; } if (!bInput && N->IsA()) { TargetNode = Cast(N); break; } } if (!bInput && !TargetNode) { // Auto-spawn result node if missing FString NewGuid = SpawnFunctionResultNode(Blueprint, G, FVector2D(600, 0)); if (UEdGraphNode* R = FindNodeByGuid(G, NewGuid)) TargetNode = Cast(R); } if (!TargetNode) return false; TSharedPtr NewPinInfo = MakeShareable(new FUserPinInfo()); NewPinInfo->PinName = ParamName; NewPinInfo->PinType = PinType; NewPinInfo->DesiredPinDirection = bInput ? EGPD_Output : EGPD_Input; TargetNode->UserDefinedPins.Add(NewPinInfo); TargetNode->ReconstructNode(); FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); return true; } bool UMCPGraphLibrary::RemoveFunctionParameter(UBlueprint* Blueprint, FName FunctionName, FName ParamName) { if (!Blueprint || FunctionName.IsNone() || ParamName.IsNone()) return false; UEdGraph* G = FindFunctionGraph(Blueprint, FunctionName); if (!G) return false; bool bChanged = false; for (UEdGraphNode* N : G->Nodes) { UK2Node_EditablePinBase* E = Cast(N); if (!E) continue; for (int32 i = E->UserDefinedPins.Num() - 1; i >= 0; --i) { if (E->UserDefinedPins[i]->PinName == ParamName) { E->RemoveUserDefinedPin(E->UserDefinedPins[i]); bChanged = true; } } if (bChanged) E->ReconstructNode(); } if (bChanged) FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint); return bChanged; } // =========================================================================== // UMG Designer / WidgetTree // =========================================================================== FString UMCPGraphLibrary::DescribeWidgetTreeJson(UWidgetBlueprint* Blueprint) { TSharedRef Root = MakeShared(); UWidgetTree* Tree = MCPGetWidgetTree(Blueprint); if (!Blueprint || !Tree) { Root->SetBoolField(TEXT("ok"), false); Root->SetStringField(TEXT("error"), TEXT("not a WidgetBlueprint or missing WidgetTree")); return MCPWidgetJsonStringify(Root); } Root->SetBoolField(TEXT("ok"), true); Root->SetStringField(TEXT("path"), Blueprint->GetPathName()); if (Tree->RootWidget) { Root->SetStringField(TEXT("root"), Tree->RootWidget->GetFName().ToString()); } TArray Widgets; Tree->GetAllWidgets(Widgets); TArray> Items; for (UWidget* Widget : Widgets) { if (Widget) { Items.Add(MakeShared(WidgetToJson(Widget))); } } Root->SetArrayField(TEXT("widgets"), Items); Root->SetNumberField(TEXT("count"), Items.Num()); return MCPWidgetJsonStringify(Root); } FString UMCPGraphLibrary::GetWidgetPropertiesJson(UWidgetBlueprint* Blueprint, FName WidgetName) { return MCPWidgetJsonStringify(ExportObjectPropertiesJson(MCPFindWidget(Blueprint, WidgetName))); } FString UMCPGraphLibrary::GetWidgetSlotPropertiesJson(UWidgetBlueprint* Blueprint, FName WidgetName) { UWidget* Widget = MCPFindWidget(Blueprint, WidgetName); return MCPWidgetJsonStringify(ExportObjectPropertiesJson(Widget ? Widget->Slot : nullptr)); } FString UMCPGraphLibrary::AddWidgetToTree(UWidgetBlueprint* Blueprint, UClass* WidgetClass, FName WidgetName, FName ParentName, int32 Index, bool bIsVariable) { UWidgetTree* Tree = MCPGetWidgetTree(Blueprint); if (!Blueprint || !Tree || !WidgetClass || !WidgetClass->IsChildOf(UWidget::StaticClass())) { return FString(); } FName FinalName = WidgetName; if (FinalName.IsNone()) { FinalName = MakeUniqueObjectName(Tree, WidgetClass, WidgetClass->GetFName()); } else if (Tree->FindWidget(FinalName)) { return FString(); } UWidget* Parent = ParentName.IsNone() ? nullptr : MCPFindWidget(Blueprint, ParentName); if (!ParentName.IsNone() && !Parent) { return FString(); } Tree->SetFlags(RF_Transactional); Tree->Modify(); UWidget* Widget = Tree->ConstructWidget(WidgetClass, FinalName); if (!Widget) { return FString(); } Widget->SetFlags(RF_Transactional); Widget->CreatedFromPalette(); Widget->bIsVariable = bIsVariable; if (!AddExistingWidgetToParent(Blueprint, Widget, Parent, Index)) { Tree->RemoveWidget(Widget); return FString(); } if (Widget->bIsVariable) { Blueprint->OnVariableAdded(Widget->GetFName()); } MarkWidgetBPDirty(Blueprint); return Widget->GetFName().ToString(); } bool UMCPGraphLibrary::RemoveWidgetFromTree(UWidgetBlueprint* Blueprint, FName WidgetName) { UWidget* Widget = MCPFindWidget(Blueprint, WidgetName); if (!Blueprint || !Widget) { return false; } const bool bWasVariable = Widget->bIsVariable; const FName RemovedName = Widget->GetFName(); TSet Widgets; Widgets.Add(Widget); FWidgetBlueprintEditorUtils::DeleteWidgets(Blueprint, Widgets, FWidgetBlueprintEditorUtils::EDeleteWidgetWarningType::DeleteSilently); if (bWasVariable) { Blueprint->OnVariableRemoved(RemovedName); } MarkWidgetBPDirty(Blueprint); return true; } bool UMCPGraphLibrary::RenameWidgetInTree(UWidgetBlueprint* Blueprint, FName OldName, FName NewName) { UWidgetTree* Tree = MCPGetWidgetTree(Blueprint); UWidget* Widget = MCPFindWidget(Blueprint, OldName); if (!Blueprint || !Tree || !Widget || NewName.IsNone() || Tree->FindWidget(NewName)) { return false; } const bool bWasVariable = Widget->bIsVariable; Widget->SetFlags(RF_Transactional); Widget->Modify(); const FName PreviousName = Widget->GetFName(); if (!Widget->Rename(*NewName.ToString(), Tree, REN_DontCreateRedirectors)) { return false; } Widget->SetDisplayLabel(TEXT("")); if (bWasVariable) { Blueprint->OnVariableRenamed(PreviousName, NewName); } MarkWidgetBPDirty(Blueprint); return true; } bool UMCPGraphLibrary::MoveWidgetInTree(UWidgetBlueprint* Blueprint, FName WidgetName, FName NewParentName, int32 Index) { UWidgetTree* Tree = MCPGetWidgetTree(Blueprint); UWidget* Widget = MCPFindWidget(Blueprint, WidgetName); UWidget* NewParent = NewParentName.IsNone() ? nullptr : MCPFindWidget(Blueprint, NewParentName); if (!Blueprint || !Tree || !Widget || (!NewParentName.IsNone() && !NewParent) || Widget == NewParent) { return false; } if (NewParent && NewParent->IsChildOf(Widget)) { return false; } Tree->Modify(); if (Widget == Tree->RootWidget) { Tree->RootWidget = nullptr; } else if (UPanelWidget* OldParent = Cast(Widget->Slot ? Widget->Slot->Parent : nullptr)) { OldParent->Modify(); OldParent->RemoveChild(Widget); } else { Tree->RemoveWidget(Widget); UWidgetTree::TryMoveWidgetToNewTree(Widget, Tree); } if (!AddExistingWidgetToParent(Blueprint, Widget, NewParent, Index)) { return false; } MarkWidgetBPDirty(Blueprint); return true; } bool UMCPGraphLibrary::SetRootWidget(UWidgetBlueprint* Blueprint, FName WidgetName) { UWidgetTree* Tree = MCPGetWidgetTree(Blueprint); UWidget* Widget = MCPFindWidget(Blueprint, WidgetName); if (!Blueprint || !Tree || !Widget) { return false; } Tree->Modify(); if (UPanelWidget* OldParent = Cast(Widget->Slot ? Widget->Slot->Parent : nullptr)) { OldParent->Modify(); OldParent->RemoveChild(Widget); } Tree->RootWidget = Widget; MarkWidgetBPDirty(Blueprint); return true; } bool UMCPGraphLibrary::SetWidgetIsVariable(UWidgetBlueprint* Blueprint, FName WidgetName, bool bIsVariable) { UWidget* Widget = MCPFindWidget(Blueprint, WidgetName); if (!Blueprint || !Widget || Widget->bIsVariable == bIsVariable) { return Widget != nullptr; } Widget->Modify(); Widget->bIsVariable = bIsVariable; if (bIsVariable) { Blueprint->OnVariableAdded(Widget->GetFName()); } else { Blueprint->OnVariableRemoved(Widget->GetFName()); } MarkWidgetBPDirty(Blueprint); return true; } bool UMCPGraphLibrary::SetWidgetProperty(UWidgetBlueprint* Blueprint, FName WidgetName, FName PropertyName, const FString& Value) { UWidget* Widget = MCPFindWidget(Blueprint, WidgetName); const bool bOk = SetObjectPropertyFromString(Widget, PropertyName, Value); if (bOk) { MarkWidgetBPDirty(Blueprint, false); } return bOk; } bool UMCPGraphLibrary::SetWidgetSlotProperty(UWidgetBlueprint* Blueprint, FName WidgetName, FName PropertyName, const FString& Value) { UWidget* Widget = MCPFindWidget(Blueprint, WidgetName); const bool bOk = SetObjectPropertyFromString(Widget ? Widget->Slot : nullptr, PropertyName, Value); if (bOk) { MarkWidgetBPDirty(Blueprint, false); } return bOk; } bool UMCPGraphLibrary::SetCanvasSlotLayout(UWidgetBlueprint* Blueprint, FName WidgetName, FVector2D Position, FVector2D Size, FVector2D AnchorsMinimum, FVector2D AnchorsMaximum, FVector2D Alignment, bool bAutoSize, int32 ZOrder) { UWidget* Widget = MCPFindWidget(Blueprint, WidgetName); UCanvasPanelSlot* Slot = Widget ? Cast(Widget->Slot) : nullptr; if (!Slot) { return false; } Slot->SetFlags(RF_Transactional); Slot->Modify(); Slot->SetPosition(Position); Slot->SetSize(Size); Slot->SetAnchors(FAnchors(AnchorsMinimum.X, AnchorsMinimum.Y, AnchorsMaximum.X, AnchorsMaximum.Y)); Slot->SetAlignment(Alignment); Slot->SetAutoSize(bAutoSize); Slot->SetZOrder(ZOrder); Slot->SynchronizeProperties(); MarkWidgetBPDirty(Blueprint, false); return true; } bool UMCPGraphLibrary::SetNamedSlotContent(UWidgetBlueprint* Blueprint, FName HostWidgetName, FName SlotName, FName ContentWidgetName) { UWidget* Host = MCPFindWidget(Blueprint, HostWidgetName); UWidget* Content = MCPFindWidget(Blueprint, ContentWidgetName); INamedSlotInterface* NamedSlots = Cast(Host); if (!Blueprint || !NamedSlots || !Content || SlotName.IsNone()) { return false; } Host->Modify(); Content->Modify(); NamedSlots->SetContentForSlot(SlotName, Content); MarkWidgetBPDirty(Blueprint); return true; } bool UMCPGraphLibrary::ClearNamedSlotContent(UWidgetBlueprint* Blueprint, FName HostWidgetName, FName SlotName) { UWidget* Host = MCPFindWidget(Blueprint, HostWidgetName); INamedSlotInterface* NamedSlots = Cast(Host); if (!Blueprint || !NamedSlots || SlotName.IsNone()) { return false; } Host->Modify(); NamedSlots->SetContentForSlot(SlotName, nullptr); MarkWidgetBPDirty(Blueprint); return true; } bool UMCPGraphLibrary::SetDesiredFocusWidget(UWidgetBlueprint* Blueprint, FName WidgetName) { if (!Blueprint || WidgetName.IsNone() || !MCPFindWidget(Blueprint, WidgetName)) { return false; } FWidgetBlueprintEditorUtils::SetDesiredFocus(Blueprint, WidgetName); MarkWidgetBPDirty(Blueprint, false); return true; } bool UMCPGraphLibrary::CreateWidgetFloatAnimation(UWidgetBlueprint* Blueprint, FName AnimName, FName WidgetName, FName PropertyName, const TArray& Times, const TArray& Values) { if (!Blueprint || AnimName.IsNone() || WidgetName.IsNone() || PropertyName.IsNone()) return false; if (Times.Num() == 0 || Times.Num() != Values.Num()) return false; UWidget* Target = MCPFindWidget(Blueprint, WidgetName); if (!Target) return false; // No duplicates. for (UWidgetAnimation* Existing : Blueprint->Animations) if (Existing && Existing->GetFName() == AnimName) return false; UWidgetAnimation* Anim = NewObject(Blueprint, AnimName, RF_Transactional); Anim->MovieScene = NewObject(Anim, AnimName, RF_Transactional); // Millisecond tick resolution keeps second->frame conversion exact for our keys. Anim->MovieScene->SetTickResolutionDirectly(FFrameRate(1000, 1)); Anim->MovieScene->SetDisplayRate(FFrameRate(60, 1)); // Possess the target widget and register the runtime binding. const FGuid BindingGuid = Anim->MovieScene->AddPossessable(WidgetName.ToString(), Target->GetClass()); FWidgetAnimationBinding NewBinding; NewBinding.WidgetName = WidgetName; NewBinding.AnimationGuid = BindingGuid; NewBinding.bIsRootWidget = false; Anim->AnimationBindings.Add(NewBinding); // Float track on the requested property (e.g. RenderOpacity). UMovieSceneFloatTrack* Track = Anim->MovieScene->AddTrack(BindingGuid); if (!Track) return false; Track->SetPropertyNameAndPath(PropertyName, PropertyName.ToString()); UMovieSceneFloatSection* Section = Cast(Track->CreateNewSection()); if (!Section) return false; Track->AddSection(*Section); TArrayView Channels = Section->GetChannelProxy().GetChannels(); if (Channels.Num() == 0) return false; FMovieSceneFloatChannel* Channel = Channels[0]; int32 LastFrame = 0; for (int32 i = 0; i < Times.Num(); ++i) { const int32 FrameMs = FMath::RoundToInt(Times[i] * 1000.0f); Channel->AddLinearKey(FFrameNumber(FrameMs), Values[i]); LastFrame = FMath::Max(LastFrame, FrameMs); } const TRange PlaybackRange(FFrameNumber(0), FFrameNumber(LastFrame)); Section->SetRange(PlaybackRange); Anim->MovieScene->SetPlaybackRange(PlaybackRange, /*bAlwaysMarkDirty*/ false); Blueprint->Animations.Add(Anim); MarkWidgetBPDirty(Blueprint, /*bStructural*/ true); return true; } // =========================================================================== // Compile diagnostics + graph dump // =========================================================================== TArray UMCPGraphLibrary::CompileWithMessages(UBlueprint* Blueprint) { TArray Out; if (!Blueprint) return Out; FCompilerResultsLog Results; Results.SetSourcePath(Blueprint->GetPathName()); Results.BeginEvent(TEXT("MCPCompile")); FKismetEditorUtilities::CompileBlueprint(Blueprint, EBlueprintCompileOptions::None, &Results); Results.EndEvent(); auto AppendList = [&Out](const TArray>& Msgs, const TCHAR* Sev) { for (const TSharedRef& M : Msgs) { FString NodeName; for (const TSharedRef& Tk : M->GetMessageTokens()) { if (Tk->GetType() == EMessageToken::Object) { TSharedRef Obj = StaticCastSharedRef(Tk); if (UObject* Resolved = Obj->GetObject().Get()) { NodeName = Resolved->GetName(); break; } } } Out.Add(FString::Printf(TEXT("%s|%s|%s"), Sev, *NodeName, *M->ToText().ToString())); } }; AppendList(Results.Messages, TEXT("INFO")); return Out; } static TSharedRef NodeToJson(UEdGraphNode* N) { TSharedRef J = MakeShared(); J->SetStringField(TEXT("guid"), N->NodeGuid.ToString()); J->SetStringField(TEXT("class"), N->GetClass()->GetName()); J->SetNumberField(TEXT("x"), N->NodePosX); J->SetNumberField(TEXT("y"), N->NodePosY); J->SetStringField(TEXT("title"), N->GetNodeTitle(ENodeTitleType::ListView).ToString()); if (!N->NodeComment.IsEmpty()) J->SetStringField(TEXT("comment"), N->NodeComment); if (UEdGraphNode_Comment* CN = Cast(N)) { J->SetNumberField(TEXT("w"), CN->NodeWidth); J->SetNumberField(TEXT("h"), CN->NodeHeight); if (!CN->NodeComment.IsEmpty()) J->SetStringField(TEXT("comment"), CN->NodeComment); } // Type-specific target hint FString Target; if (UK2Node_CallFunction* CF = Cast(N)) { const FMemberReference& Ref = CF->FunctionReference; UClass* OwnerCls = Ref.GetMemberParentClass(); Target = FString::Printf(TEXT("%s.%s"), OwnerCls ? *OwnerCls->GetName() : TEXT("?"), *Ref.GetMemberName().ToString()); } else if (UK2Node_Event* EV = Cast(N)) { Target = EV->EventReference.GetMemberName().ToString(); } else if (UK2Node_VariableGet* VG = Cast(N)) { Target = VG->VariableReference.GetMemberName().ToString(); } else if (UK2Node_VariableSet* VS = Cast(N)) { Target = VS->VariableReference.GetMemberName().ToString(); } if (!Target.IsEmpty()) J->SetStringField(TEXT("target"), Target); TArray> Pins; for (UEdGraphPin* P : N->Pins) { if (!P) continue; TSharedRef PJ = MakeShared(); PJ->SetStringField(TEXT("name"), P->PinName.ToString()); PJ->SetStringField(TEXT("dir"), P->Direction == EGPD_Input ? TEXT("in") : TEXT("out")); PJ->SetStringField(TEXT("type"), P->PinType.PinCategory.ToString()); if (!P->PinType.PinSubCategory.IsNone()) PJ->SetStringField(TEXT("subtype"), P->PinType.PinSubCategory.ToString()); if (P->PinType.PinSubCategoryObject.IsValid()) PJ->SetStringField(TEXT("subobj"), P->PinType.PinSubCategoryObject->GetPathName()); if (P->PinType.IsArray()) PJ->SetBoolField(TEXT("array"), true); if (P->PinType.IsSet()) PJ->SetBoolField(TEXT("set"), true); if (P->PinType.IsMap()) PJ->SetBoolField(TEXT("map"), true); if (P->PinType.bIsReference) PJ->SetBoolField(TEXT("byref"), true); if (!P->DefaultValue.IsEmpty()) PJ->SetStringField(TEXT("default"), P->DefaultValue); if (P->DefaultObject) PJ->SetStringField(TEXT("default_obj"), P->DefaultObject->GetPathName()); TArray> Links; for (UEdGraphPin* L : P->LinkedTo) { if (!L || !L->GetOwningNode()) continue; TSharedRef LJ = MakeShared(); LJ->SetStringField(TEXT("node"), L->GetOwningNode()->NodeGuid.ToString()); LJ->SetStringField(TEXT("pin"), L->PinName.ToString()); Links.Add(MakeShared(LJ)); } if (Links.Num()) PJ->SetArrayField(TEXT("links"), Links); Pins.Add(MakeShared(PJ)); } J->SetArrayField(TEXT("pins"), Pins); return J; } FString UMCPGraphLibrary::DumpGraphToJson(UEdGraph* Graph) { if (!Graph) return TEXT("{\"error\":\"null graph\"}"); TSharedRef Root = MakeShared(); Root->SetStringField(TEXT("graph_name"), Graph->GetName()); TArray> Nodes; for (UEdGraphNode* N : Graph->Nodes) { if (!N) continue; Nodes.Add(MakeShared(NodeToJson(N))); } Root->SetArrayField(TEXT("nodes"), Nodes); FString Out; TSharedRef> Writer = TJsonWriterFactory<>::Create(&Out); FJsonSerializer::Serialize(Root, Writer); return Out; } // =========================================================================== // Discovery v0.3 // =========================================================================== static FString JsonStringify(TSharedRef Obj) { FString Out; TSharedRef> W = TJsonWriterFactory<>::Create(&Out); FJsonSerializer::Serialize(Obj, W); return Out; } FString UMCPGraphLibrary::ListGraphsJson(UBlueprint* Blueprint) { TSharedRef Root = MakeShared(); if (!Blueprint) { Root->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(Root); } auto AsList = [](const TArray& Gs) { TArray> A; for (UEdGraph* G : Gs) if (G) A.Add(MakeShared(G->GetName())); return A; }; Root->SetArrayField(TEXT("event"), AsList(Blueprint->UbergraphPages)); Root->SetArrayField(TEXT("functions"), AsList(Blueprint->FunctionGraphs)); Root->SetArrayField(TEXT("macros"), AsList(Blueprint->MacroGraphs)); Root->SetArrayField(TEXT("delegates"), AsList(Blueprint->DelegateSignatureGraphs)); return JsonStringify(Root); } FString UMCPGraphLibrary::DescribeBlueprintJson(UBlueprint* Blueprint) { TSharedRef Root = MakeShared(); if (!Blueprint) { Root->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(Root); } Root->SetStringField(TEXT("path"), Blueprint->GetPathName()); Root->SetStringField(TEXT("parent"), Blueprint->ParentClass ? Blueprint->ParentClass->GetPathName() : TEXT("?")); // Interfaces TArray> Interfaces; for (const FBPInterfaceDescription& I : Blueprint->ImplementedInterfaces) { if (I.Interface) Interfaces.Add(MakeShared(I.Interface->GetPathName())); } Root->SetArrayField(TEXT("interfaces"), Interfaces); // Variables TArray> Vars; for (const FBPVariableDescription& V : Blueprint->NewVariables) { TSharedRef VJ = MakeShared(); VJ->SetStringField(TEXT("name"), V.VarName.ToString()); VJ->SetStringField(TEXT("type"), V.VarType.PinCategory.ToString()); if (V.VarType.PinSubCategoryObject.IsValid()) VJ->SetStringField(TEXT("subobj"), V.VarType.PinSubCategoryObject->GetPathName()); VJ->SetBoolField(TEXT("instance_editable"), !(V.PropertyFlags & CPF_DisableEditOnInstance)); VJ->SetBoolField(TEXT("expose_on_spawn"), !!(V.PropertyFlags & CPF_ExposeOnSpawn)); if (!V.DefaultValue.IsEmpty()) VJ->SetStringField(TEXT("default"), V.DefaultValue); if (!V.Category.IsEmpty()) VJ->SetStringField(TEXT("category"), V.Category.ToString()); Vars.Add(MakeShared(VJ)); } Root->SetArrayField(TEXT("variables"), Vars); // Components (SCS) TArray> Comps; if (USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript) { for (USCS_Node* SN : SCS->GetAllNodes()) { if (!SN) continue; TSharedRef CJ = MakeShared(); CJ->SetStringField(TEXT("name"), SN->GetVariableName().ToString()); if (SN->ComponentTemplate) CJ->SetStringField(TEXT("class"), SN->ComponentTemplate->GetClass()->GetPathName()); if (!SN->AttachToName.IsNone()) CJ->SetStringField(TEXT("socket"), SN->AttachToName.ToString()); USCS_Node* P = nullptr; for (USCS_Node* M : SCS->GetAllNodes()) if (M && M->GetChildNodes().Contains(SN)) { P = M; break; } if (P) CJ->SetStringField(TEXT("parent"), P->GetVariableName().ToString()); Comps.Add(MakeShared(CJ)); } } Root->SetArrayField(TEXT("components"), Comps); // Functions TArray> Fns; for (UEdGraph* G : Blueprint->FunctionGraphs) if (G) Fns.Add(MakeShared(G->GetName())); Root->SetArrayField(TEXT("functions"), Fns); // Dispatchers (multicast delegate props on generated class) TArray> Disps; if (UClass* GC = Blueprint->GeneratedClass) { for (TFieldIterator It(GC, EFieldIteratorFlags::ExcludeSuper); It; ++It) { FMulticastDelegateProperty* D = *It; if (D) Disps.Add(MakeShared(D->GetName())); } } Root->SetArrayField(TEXT("dispatchers"), Disps); return JsonStringify(Root); } FString UMCPGraphLibrary::GetSelectedNodesJson(UBlueprint* Blueprint) { TSharedRef Root = MakeShared(); if (!Blueprint || !GEditor) { Root->SetStringField(TEXT("error"), TEXT("no editor or bp")); return JsonStringify(Root); } UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem(); IAssetEditorInstance* Inst = AES ? AES->FindEditorForAsset(Blueprint, /*bFocusIfOpen*/ false) : nullptr; if (!Inst) { Root->SetStringField(TEXT("error"), TEXT("blueprint not open in editor")); return JsonStringify(Root); } // BlueprintEditor inherits from FAssetEditorToolkit which inherits from IAssetEditorInstance. // Cast guarded by editor-name check. if (Inst->GetEditorName() != FName(TEXT("BlueprintEditor"))) { Root->SetStringField(TEXT("error"), TEXT("active editor is not BlueprintEditor")); return JsonStringify(Root); } FBlueprintEditor* BPE = static_cast(Inst); FGraphPanelSelectionSet Sel = BPE->GetSelectedNodes(); TArray> Arr; for (UObject* O : Sel) { if (UEdGraphNode* N = Cast(O)) { Arr.Add(MakeShared(NodeToJson(N))); } } Root->SetArrayField(TEXT("selected"), Arr); // Active graph if (UEdGraph* Focused = BPE->GetFocusedGraph()) { Root->SetStringField(TEXT("focused_graph"), Focused->GetName()); } return JsonStringify(Root); } TArray UMCPGraphLibrary::ListOpenBlueprints() { TArray Out; if (!GEditor) return Out; UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem(); if (!AES) return Out; TArray Assets = AES->GetAllEditedAssets(); for (UObject* A : Assets) if (UBlueprint* BP = Cast(A)) Out.Add(BP->GetPathName()); return Out; } FString UMCPGraphLibrary::ResolveFunctionSourceJson(UEdGraph* Graph, const FString& NodeId) { TSharedRef R = MakeShared(); UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) { R->SetStringField(TEXT("error"), TEXT("node not found")); return JsonStringify(R); } UK2Node_CallFunction* CF = Cast(N); if (!CF) { R->SetStringField(TEXT("error"), TEXT("not a CallFunction node")); return JsonStringify(R); } UClass* Owner = CF->FunctionReference.GetMemberParentClass(); FName Fn = CF->FunctionReference.GetMemberName(); R->SetStringField(TEXT("function"), Fn.ToString()); R->SetStringField(TEXT("owner_class"), Owner ? Owner->GetPathName() : TEXT("?")); R->SetBoolField(TEXT("native"), Owner && Owner->HasAllClassFlags(CLASS_Native)); if (Owner && !Owner->HasAllClassFlags(CLASS_Native)) { if (UBlueprint* SrcBP = Cast(Owner->ClassGeneratedBy)) { R->SetStringField(TEXT("source_bp_path"), SrcBP->GetPathName()); UEdGraph* FG = nullptr; for (UEdGraph* G : SrcBP->FunctionGraphs) if (G && G->GetFName() == Fn) { FG = G; break; } if (FG) R->SetStringField(TEXT("source_graph"), FG->GetName()); } } return JsonStringify(R); } static void CollectRefsToVariable(UBlueprint* BP, FName Name, TArray>& Out) { TArray AllGraphs; BP->GetAllGraphs(AllGraphs); for (UEdGraph* G : AllGraphs) { if (!G) continue; for (UEdGraphNode* N : G->Nodes) { UK2Node_VariableGet* VG = Cast(N); UK2Node_VariableSet* VS = Cast(N); FName MemberName = NAME_None; if (VG) MemberName = VG->VariableReference.GetMemberName(); else if (VS) MemberName = VS->VariableReference.GetMemberName(); if (MemberName == Name) { TSharedRef J = MakeShared(); J->SetStringField(TEXT("graph"), G->GetName()); J->SetStringField(TEXT("guid"), N->NodeGuid.ToString()); J->SetStringField(TEXT("kind"), VG ? TEXT("get") : TEXT("set")); Out.Add(MakeShared(J)); } } } } FString UMCPGraphLibrary::FindVariableReferencesJson(UBlueprint* Blueprint, FName VariableName) { TSharedRef R = MakeShared(); if (!Blueprint) { R->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(R); } TArray> Refs; CollectRefsToVariable(Blueprint, VariableName, Refs); R->SetArrayField(TEXT("references"), Refs); R->SetNumberField(TEXT("count"), Refs.Num()); return JsonStringify(R); } FString UMCPGraphLibrary::FindFunctionReferencesJson(UBlueprint* Blueprint, FName FunctionName) { TSharedRef R = MakeShared(); if (!Blueprint) { R->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(R); } TArray AllGraphs; Blueprint->GetAllGraphs(AllGraphs); TArray> Refs; for (UEdGraph* G : AllGraphs) { if (!G) continue; for (UEdGraphNode* N : G->Nodes) { UK2Node_CallFunction* CF = Cast(N); if (!CF) continue; if (CF->FunctionReference.GetMemberName() == FunctionName) { TSharedRef J = MakeShared(); J->SetStringField(TEXT("graph"), G->GetName()); J->SetStringField(TEXT("guid"), N->NodeGuid.ToString()); UClass* O = CF->FunctionReference.GetMemberParentClass(); J->SetStringField(TEXT("owner"), O ? O->GetPathName() : TEXT("?")); Refs.Add(MakeShared(J)); } } } R->SetArrayField(TEXT("references"), Refs); R->SetNumberField(TEXT("count"), Refs.Num()); return JsonStringify(R); } TArray UMCPGraphLibrary::FindBlueprintsByParent(const FString& ParentClassPath) { TArray Out; IAssetRegistry& AR = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); FARFilter Filter; Filter.ClassPaths.Add(FTopLevelAssetPath(TEXT("/Script/Engine"), TEXT("Blueprint"))); Filter.bRecursiveClasses = true; TArray Assets; AR.GetAssets(Filter, Assets); for (const FAssetData& AD : Assets) { const FString Parent = AD.GetTagValueRef(FBlueprintTags::ParentClassPath); if (Parent == ParentClassPath || Parent.Contains(ParentClassPath)) Out.Add(AD.GetSoftObjectPath().ToString()); } return Out; } TArray UMCPGraphLibrary::GetReferencedAssets(const FString& AssetPath) { TArray Out; IAssetRegistry& AR = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); TArray Deps; AR.GetDependencies(FName(*FPackageName::ObjectPathToPackageName(AssetPath)), Deps); for (FName D : Deps) Out.Add(D.ToString()); return Out; } TArray UMCPGraphLibrary::GetReferencersOfAsset(const FString& AssetPath) { TArray Out; IAssetRegistry& AR = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")).Get(); TArray Refs; AR.GetReferencers(FName(*FPackageName::ObjectPathToPackageName(AssetPath)), Refs); for (FName R : Refs) Out.Add(R.ToString()); return Out; } // =========================================================================== // Editing v0.3 // =========================================================================== int32 UMCPGraphLibrary::MoveNodes(UEdGraph* Graph, const TArray& NodeIds, const TArray& Positions) { if (!Graph || NodeIds.Num() != Positions.Num()) return 0; int32 Moved = 0; for (int32 i = 0; i < NodeIds.Num(); ++i) { if (UEdGraphNode* N = FindNodeByGuid(Graph, NodeIds[i])) { N->NodePosX = static_cast(Positions[i].X); N->NodePosY = static_cast(Positions[i].Y); ++Moved; } } if (Moved && Graph) if (UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraph(Graph)) MarkBPDirty(BP); return Moved; } bool UMCPGraphLibrary::ResizeCommentNode(UEdGraph* Graph, const FString& NodeId, FVector2D Size) { UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); UEdGraphNode_Comment* CN = Cast(N); if (!CN) return false; CN->NodeWidth = FMath::Max(64, (int32)Size.X); CN->NodeHeight = FMath::Max(64, (int32)Size.Y); return true; } bool UMCPGraphLibrary::SetNodeComment(UEdGraph* Graph, const FString& NodeId, const FString& Comment, bool bCommentBubbleVisible) { UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) return false; N->NodeComment = Comment; N->bCommentBubbleVisible = bCommentBubbleVisible; return true; } // =========================================================================== // Navigation v0.3 // =========================================================================== bool UMCPGraphLibrary::OpenBlueprintEditor(UBlueprint* Blueprint) { if (!Blueprint || !GEditor) return false; UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem(); return AES && AES->OpenEditorForAsset(Blueprint); } bool UMCPGraphLibrary::OpenGraphInEditor(UBlueprint* Blueprint, UEdGraph* Graph) { if (!OpenBlueprintEditor(Blueprint) || !Graph) return false; UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem(); IAssetEditorInstance* Inst = AES->FindEditorForAsset(Blueprint, true); if (!Inst || Inst->GetEditorName() != FName(TEXT("BlueprintEditor"))) return false; FBlueprintEditor* BPE = static_cast(Inst); BPE->OpenDocument(Graph, FDocumentTracker::OpenNewDocument); return true; } bool UMCPGraphLibrary::JumpToNodeInEditor(UBlueprint* Blueprint, UEdGraph* Graph, const FString& NodeId) { if (!OpenGraphInEditor(Blueprint, Graph)) return false; UEdGraphNode* N = FindNodeByGuid(Graph, NodeId); if (!N) return false; UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem(); IAssetEditorInstance* Inst = AES->FindEditorForAsset(Blueprint, true); FBlueprintEditor* BPE = static_cast(Inst); BPE->JumpToNode(N, /*bRequestRename*/ false); return true; }