Merge of the two project copies into one self-contained plugin (the superset: variable_op + variables.py, full pcg_op runtime/declarative/preset ops, the CreateWidgetFloatAnimation widget tool, and full Voxel graph authoring). Made fully project- and machine-agnostic — no hardcoded paths: - New src/projectPaths.js auto-detects the host .uproject (walk-up), project name, Editor build target, log file, and engine install (EngineAssociation via launcher manifest/registry, else installed-engine scan). All overridable via UE_* env vars. - Rewired buildOrchestrator/insights/launcher/insightsExporter/server.js and the Python workers (cpp_scaffold, live_coding, apply_graph, console) off the old C:/Github/ihy, IHY*, E:/UE_Versions and UE_5.7 literals onto the resolver. - Voxel made optional: Build.cs auto-detects the Voxel plugin (env UE_MCP_WITH_VOXEL override) and the C++ compiles to stubs under WITH_MCP_VOXEL, so the module builds in projects without Voxel; .uplugin marks Voxel optional. - De-branded the agent-gateway and docs; scrubbed a leaked API key; excluded node_modules/Binaries/Intermediate/__pycache__/secrets from the repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2373 lines
91 KiB
C++
2373 lines
91 KiB
C++
#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 <typename TNode>
|
|
TNode* UMCPGraphLibrary::PlaceNode(UEdGraph* Graph, FVector2D Position)
|
|
{
|
|
if (!Graph) return nullptr;
|
|
TNode* Node = NewObject<TNode>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<FJsonObject>& Object)
|
|
{
|
|
FString Out;
|
|
const TSharedRef<TJsonWriter<>> 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<FProperty>(Object->GetClass(), PropertyName);
|
|
if (!Property) return false;
|
|
Object->SetFlags(RF_Transactional);
|
|
Object->Modify();
|
|
void* PropertyValue = Property->ContainerPtrToValuePtr<void>(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<FJsonObject> ExportObjectPropertiesJson(UObject* Object)
|
|
{
|
|
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
|
|
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<FName, FString> Props;
|
|
FWidgetBlueprintEditorUtils::ExportPropertiesToText(Object, Props);
|
|
TSharedRef<FJsonObject> PropJson = MakeShared<FJsonObject>();
|
|
for (const TPair<FName, FString>& Pair : Props)
|
|
{
|
|
PropJson->SetStringField(Pair.Key.ToString(), Pair.Value);
|
|
}
|
|
Root->SetObjectField(TEXT("properties"), PropJson);
|
|
return Root;
|
|
}
|
|
|
|
static TSharedRef<FJsonObject> WidgetSlotJson(UWidget* Widget)
|
|
{
|
|
TSharedRef<FJsonObject> SlotJson = MakeShared<FJsonObject>();
|
|
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<UCanvasPanelSlot>(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<FJsonValueNumber>(Pos.X),
|
|
MakeShared<FJsonValueNumber>(Pos.Y)
|
|
});
|
|
SlotJson->SetArrayField(TEXT("size"), {
|
|
MakeShared<FJsonValueNumber>(Size.X),
|
|
MakeShared<FJsonValueNumber>(Size.Y)
|
|
});
|
|
SlotJson->SetArrayField(TEXT("alignment"), {
|
|
MakeShared<FJsonValueNumber>(Align.X),
|
|
MakeShared<FJsonValueNumber>(Align.Y)
|
|
});
|
|
SlotJson->SetArrayField(TEXT("anchors_min"), {
|
|
MakeShared<FJsonValueNumber>(Anchors.Minimum.X),
|
|
MakeShared<FJsonValueNumber>(Anchors.Minimum.Y)
|
|
});
|
|
SlotJson->SetArrayField(TEXT("anchors_max"), {
|
|
MakeShared<FJsonValueNumber>(Anchors.Maximum.X),
|
|
MakeShared<FJsonValueNumber>(Anchors.Maximum.Y)
|
|
});
|
|
SlotJson->SetBoolField(TEXT("auto_size"), CanvasSlot->GetAutoSize());
|
|
SlotJson->SetNumberField(TEXT("z_order"), CanvasSlot->GetZOrder());
|
|
}
|
|
return SlotJson;
|
|
}
|
|
|
|
static TSharedRef<FJsonObject> WidgetToJson(UWidget* Widget)
|
|
{
|
|
TSharedRef<FJsonObject> Json = MakeShared<FJsonObject>();
|
|
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<TSharedPtr<FJsonValue>> Children;
|
|
if (UPanelWidget* Panel = Cast<UPanelWidget>(Widget))
|
|
{
|
|
const int32 ChildCount = Panel->GetChildrenCount();
|
|
for (int32 Index = 0; Index < ChildCount; ++Index)
|
|
{
|
|
if (UWidget* Child = Panel->GetChildAt(Index))
|
|
{
|
|
TSharedRef<FJsonObject> ChildRef = MakeShared<FJsonObject>();
|
|
ChildRef->SetStringField(TEXT("name"), Child->GetFName().ToString());
|
|
ChildRef->SetStringField(TEXT("class"), Child->GetClass()->GetPathName());
|
|
ChildRef->SetNumberField(TEXT("index"), Index);
|
|
Children.Add(MakeShared<FJsonValueObject>(ChildRef));
|
|
}
|
|
}
|
|
}
|
|
Json->SetArrayField(TEXT("children"), Children);
|
|
|
|
TArray<FName> SlotNames;
|
|
if (INamedSlotInterface* NamedSlots = Cast<INamedSlotInterface>(Widget))
|
|
{
|
|
NamedSlots->GetSlotNames(SlotNames);
|
|
TArray<TSharedPtr<FJsonValue>> NamedSlotValues;
|
|
for (FName SlotName : SlotNames)
|
|
{
|
|
TSharedRef<FJsonObject> Slot = MakeShared<FJsonObject>();
|
|
Slot->SetStringField(TEXT("slot"), SlotName.ToString());
|
|
if (UWidget* Content = NamedSlots->GetContentForSlot(SlotName))
|
|
{
|
|
Slot->SetStringField(TEXT("content"), Content->GetFName().ToString());
|
|
}
|
|
NamedSlotValues.Add(MakeShared<FJsonValueObject>(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<UPanelWidget>(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<FString> UMCPGraphLibrary::ListNodeIds(UEdGraph* Graph)
|
|
{
|
|
TArray<FString> Out;
|
|
if (!Graph) return Out;
|
|
for (UEdGraphNode* N : Graph->Nodes) { if (N) Out.Add(N->NodeGuid.ToString()); }
|
|
return Out;
|
|
}
|
|
|
|
TArray<FString> UMCPGraphLibrary::ListPinsOnNode(UEdGraph* Graph, const FString& NodeId)
|
|
{
|
|
TArray<FString> 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<UEdGraphNode*> 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<UK2Node_CallFunction>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->FunctionReference.SetExternalMember(FunctionName, FunctionOwnerClass);
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_Event>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->EventReference.SetExternalMember(EventName, EffectiveOwner);
|
|
Node->bOverrideFunction = true;
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<FObjectProperty>(OwnerClass, ComponentName);
|
|
if (!CompProp || !CompProp->PropertyClass) return FString();
|
|
FMulticastDelegateProperty* DelegateProp = FindFProperty<FMulticastDelegateProperty>(CompProp->PropertyClass, DelegateName);
|
|
if (!DelegateProp) return FString();
|
|
|
|
UK2Node_ComponentBoundEvent* Node = NewObject<UK2Node_ComponentBoundEvent>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->InitializeComponentBoundEventParams(CompProp, DelegateProp);
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_CustomEvent>(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<UK2Node_CustomEvent>(FindNodeByGuid(Graph, NodeIdOrEventName));
|
|
if (!EventNode)
|
|
{
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
{
|
|
UK2Node_CustomEvent* Candidate = Cast<UK2Node_CustomEvent>(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<UK2Node_IfThenElse>(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<UK2Node_ExecutionSequence>(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<UK2Node_VariableGet>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->VariableReference.SetSelfMember(VariableName);
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_VariableSet>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->VariableReference.SetSelfMember(VariableName);
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_Self>(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<UK2Node_DynamicCast>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->TargetType = TargetClass;
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_SwitchInteger>(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<UK2Node_SwitchString>(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<UK2Node_SwitchEnum>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->SetEnum(EnumType);
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_MakeArray>(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<UK2Node_MakeStruct>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->StructType = StructType;
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_BreakStruct>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->StructType = StructType;
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_SetFieldsInStruct>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->StructType = StructType;
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UBlueprint>(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<UK2Node_MacroInstance>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->SetMacroGraph(MacroGraph);
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_SpawnActorFromClass>(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<UK2Node_FormatText>(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<UK2Node_GetSubsystem>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->Initialize(SubsystemClass);
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_LoadAsset>(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<FObjectProperty>(OwnerClass, ComponentName);
|
|
if (!OutCompProp || !OutCompProp->PropertyClass) return false;
|
|
OutDelegateProp = FindFProperty<FMulticastDelegateProperty>(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<UK2Node_AddDelegate>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->SetFromProperty(DelegateProp, /*bSelfContext*/ false, DelegateProp->GetOwnerClass());
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_RemoveDelegate>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->SetFromProperty(DelegateProp, false, DelegateProp->GetOwnerClass());
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<FMulticastDelegateProperty>(OwnerClass, DispatcherName);
|
|
if (!DelegateProp) return FString();
|
|
|
|
UK2Node_CallDelegate* Node = NewObject<UK2Node_CallDelegate>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->SetFromProperty(DelegateProp, /*bSelfContext*/ true, OwnerClass);
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_AssignDelegate>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->SetFromProperty(DelegateProp, false, DelegateProp->GetOwnerClass());
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UEdGraphSchema_K2>();
|
|
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<UClass>(nullptr, *TypeSpec)
|
|
: FindFirstObject<UClass>(*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<UClass>(nullptr, *TypeSpec)
|
|
: FindFirstObject<UClass>(*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<FProperty>(Tmpl->GetClass(), PropertyName);
|
|
if (!Prop) return false;
|
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(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<USceneComponent>(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<UBoxComponent>(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<FString> UMCPGraphLibrary::GetCompileErrors(UBlueprint* Blueprint)
|
|
{
|
|
TArray<FString> 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<FString> UMCPGraphLibrary::ListFunctionsOnClass(UClass* Class)
|
|
{
|
|
TArray<FString> Out;
|
|
if (!Class) return Out;
|
|
for (TFieldIterator<UFunction> It(Class); It; ++It)
|
|
{
|
|
UFunction* F = *It;
|
|
if (!F) continue;
|
|
Out.Add(F->GetName());
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
TArray<FString> UMCPGraphLibrary::ListPropertiesOnClass(UClass* Class)
|
|
{
|
|
TArray<FString> Out;
|
|
if (!Class) return Out;
|
|
for (TFieldIterator<FProperty> It(Class); It; ++It)
|
|
{
|
|
FProperty* P = *It;
|
|
if (!P) continue;
|
|
Out.Add(FString::Printf(TEXT("%s|%s"), *P->GetName(), *P->GetCPPType()));
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
TArray<FString> UMCPGraphLibrary::ListDelegatesOnClass(UClass* Class)
|
|
{
|
|
TArray<FString> Out;
|
|
if (!Class) return Out;
|
|
for (TFieldIterator<FMulticastDelegateProperty> 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<UK2Node_Knot>(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<UEdGraphNode_Comment>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(Position.Y);
|
|
Node->NodeWidth = FMath::Max(64, static_cast<int32>(Size.X));
|
|
Node->NodeHeight = FMath::Max(64, static_cast<int32>(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<UK2Node_EnumLiteral>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->Enum = EnumType;
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_GetClassDefaults>(Graph);
|
|
Node->CreateNewGuid();
|
|
Node->NodePosX = static_cast<int32>(Position.X);
|
|
Node->NodePosY = static_cast<int32>(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<UK2Node_FunctionResult>(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<UK2Node_GetArrayItem>(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<FName, FName> 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<UObject>(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<UK2Node_Switch>(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<UK2Node_FormatText>(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<UClass>(nullptr, *TypeSpec)
|
|
: FindFirstObject<UClass>(*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<UK2Node_FunctionEntry>()) { TargetNode = Cast<UK2Node_EditablePinBase>(N); break; }
|
|
if (!bInput && N->IsA<UK2Node_FunctionResult>()) { TargetNode = Cast<UK2Node_EditablePinBase>(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<UK2Node_EditablePinBase>(R);
|
|
}
|
|
if (!TargetNode) return false;
|
|
|
|
TSharedPtr<FUserPinInfo> 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<UK2Node_EditablePinBase>(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<FJsonObject> Root = MakeShared<FJsonObject>();
|
|
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<UWidget*> Widgets;
|
|
Tree->GetAllWidgets(Widgets);
|
|
TArray<TSharedPtr<FJsonValue>> Items;
|
|
for (UWidget* Widget : Widgets)
|
|
{
|
|
if (Widget)
|
|
{
|
|
Items.Add(MakeShared<FJsonValueObject>(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<UWidget>(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<UWidget*> 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<UPanelWidget>(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<UPanelWidget>(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<UCanvasPanelSlot>(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<INamedSlotInterface>(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<INamedSlotInterface>(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<float>& Times, const TArray<float>& 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<UWidgetAnimation>(Blueprint, AnimName, RF_Transactional);
|
|
Anim->MovieScene = NewObject<UMovieScene>(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<UMovieSceneFloatTrack>(BindingGuid);
|
|
if (!Track)
|
|
return false;
|
|
Track->SetPropertyNameAndPath(PropertyName, PropertyName.ToString());
|
|
|
|
UMovieSceneFloatSection* Section = Cast<UMovieSceneFloatSection>(Track->CreateNewSection());
|
|
if (!Section)
|
|
return false;
|
|
Track->AddSection(*Section);
|
|
|
|
TArrayView<FMovieSceneFloatChannel*> Channels = Section->GetChannelProxy().GetChannels<FMovieSceneFloatChannel>();
|
|
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<FFrameNumber> 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<FString> UMCPGraphLibrary::CompileWithMessages(UBlueprint* Blueprint)
|
|
{
|
|
TArray<FString> 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<TSharedRef<FTokenizedMessage>>& Msgs, const TCHAR* Sev)
|
|
{
|
|
for (const TSharedRef<FTokenizedMessage>& M : Msgs)
|
|
{
|
|
FString NodeName;
|
|
for (const TSharedRef<IMessageToken>& Tk : M->GetMessageTokens())
|
|
{
|
|
if (Tk->GetType() == EMessageToken::Object)
|
|
{
|
|
TSharedRef<FUObjectToken> Obj = StaticCastSharedRef<FUObjectToken>(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<FJsonObject> NodeToJson(UEdGraphNode* N)
|
|
{
|
|
TSharedRef<FJsonObject> J = MakeShared<FJsonObject>();
|
|
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<UEdGraphNode_Comment>(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<UK2Node_CallFunction>(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<UK2Node_Event>(N))
|
|
{
|
|
Target = EV->EventReference.GetMemberName().ToString();
|
|
}
|
|
else if (UK2Node_VariableGet* VG = Cast<UK2Node_VariableGet>(N))
|
|
{
|
|
Target = VG->VariableReference.GetMemberName().ToString();
|
|
}
|
|
else if (UK2Node_VariableSet* VS = Cast<UK2Node_VariableSet>(N))
|
|
{
|
|
Target = VS->VariableReference.GetMemberName().ToString();
|
|
}
|
|
if (!Target.IsEmpty()) J->SetStringField(TEXT("target"), Target);
|
|
|
|
TArray<TSharedPtr<FJsonValue>> Pins;
|
|
for (UEdGraphPin* P : N->Pins)
|
|
{
|
|
if (!P) continue;
|
|
TSharedRef<FJsonObject> PJ = MakeShared<FJsonObject>();
|
|
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<TSharedPtr<FJsonValue>> Links;
|
|
for (UEdGraphPin* L : P->LinkedTo)
|
|
{
|
|
if (!L || !L->GetOwningNode()) continue;
|
|
TSharedRef<FJsonObject> LJ = MakeShared<FJsonObject>();
|
|
LJ->SetStringField(TEXT("node"), L->GetOwningNode()->NodeGuid.ToString());
|
|
LJ->SetStringField(TEXT("pin"), L->PinName.ToString());
|
|
Links.Add(MakeShared<FJsonValueObject>(LJ));
|
|
}
|
|
if (Links.Num()) PJ->SetArrayField(TEXT("links"), Links);
|
|
Pins.Add(MakeShared<FJsonValueObject>(PJ));
|
|
}
|
|
J->SetArrayField(TEXT("pins"), Pins);
|
|
return J;
|
|
}
|
|
|
|
FString UMCPGraphLibrary::DumpGraphToJson(UEdGraph* Graph)
|
|
{
|
|
if (!Graph) return TEXT("{\"error\":\"null graph\"}");
|
|
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
|
|
Root->SetStringField(TEXT("graph_name"), Graph->GetName());
|
|
TArray<TSharedPtr<FJsonValue>> Nodes;
|
|
for (UEdGraphNode* N : Graph->Nodes)
|
|
{
|
|
if (!N) continue;
|
|
Nodes.Add(MakeShared<FJsonValueObject>(NodeToJson(N)));
|
|
}
|
|
Root->SetArrayField(TEXT("nodes"), Nodes);
|
|
|
|
FString Out;
|
|
TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&Out);
|
|
FJsonSerializer::Serialize(Root, Writer);
|
|
return Out;
|
|
}
|
|
|
|
// ===========================================================================
|
|
// Discovery v0.3
|
|
// ===========================================================================
|
|
static FString JsonStringify(TSharedRef<FJsonObject> Obj)
|
|
{
|
|
FString Out;
|
|
TSharedRef<TJsonWriter<>> W = TJsonWriterFactory<>::Create(&Out);
|
|
FJsonSerializer::Serialize(Obj, W);
|
|
return Out;
|
|
}
|
|
|
|
FString UMCPGraphLibrary::ListGraphsJson(UBlueprint* Blueprint)
|
|
{
|
|
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
|
|
if (!Blueprint) { Root->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(Root); }
|
|
auto AsList = [](const TArray<UEdGraph*>& Gs)
|
|
{
|
|
TArray<TSharedPtr<FJsonValue>> A;
|
|
for (UEdGraph* G : Gs) if (G) A.Add(MakeShared<FJsonValueString>(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<FJsonObject> Root = MakeShared<FJsonObject>();
|
|
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<TSharedPtr<FJsonValue>> Interfaces;
|
|
for (const FBPInterfaceDescription& I : Blueprint->ImplementedInterfaces)
|
|
{
|
|
if (I.Interface) Interfaces.Add(MakeShared<FJsonValueString>(I.Interface->GetPathName()));
|
|
}
|
|
Root->SetArrayField(TEXT("interfaces"), Interfaces);
|
|
|
|
// Variables
|
|
TArray<TSharedPtr<FJsonValue>> Vars;
|
|
for (const FBPVariableDescription& V : Blueprint->NewVariables)
|
|
{
|
|
TSharedRef<FJsonObject> VJ = MakeShared<FJsonObject>();
|
|
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<FJsonValueObject>(VJ));
|
|
}
|
|
Root->SetArrayField(TEXT("variables"), Vars);
|
|
|
|
// Components (SCS)
|
|
TArray<TSharedPtr<FJsonValue>> Comps;
|
|
if (USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript)
|
|
{
|
|
for (USCS_Node* SN : SCS->GetAllNodes())
|
|
{
|
|
if (!SN) continue;
|
|
TSharedRef<FJsonObject> CJ = MakeShared<FJsonObject>();
|
|
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<FJsonValueObject>(CJ));
|
|
}
|
|
}
|
|
Root->SetArrayField(TEXT("components"), Comps);
|
|
|
|
// Functions
|
|
TArray<TSharedPtr<FJsonValue>> Fns;
|
|
for (UEdGraph* G : Blueprint->FunctionGraphs)
|
|
if (G) Fns.Add(MakeShared<FJsonValueString>(G->GetName()));
|
|
Root->SetArrayField(TEXT("functions"), Fns);
|
|
|
|
// Dispatchers (multicast delegate props on generated class)
|
|
TArray<TSharedPtr<FJsonValue>> Disps;
|
|
if (UClass* GC = Blueprint->GeneratedClass)
|
|
{
|
|
for (TFieldIterator<FMulticastDelegateProperty> It(GC, EFieldIteratorFlags::ExcludeSuper); It; ++It)
|
|
{
|
|
FMulticastDelegateProperty* D = *It;
|
|
if (D) Disps.Add(MakeShared<FJsonValueString>(D->GetName()));
|
|
}
|
|
}
|
|
Root->SetArrayField(TEXT("dispatchers"), Disps);
|
|
|
|
return JsonStringify(Root);
|
|
}
|
|
|
|
FString UMCPGraphLibrary::GetSelectedNodesJson(UBlueprint* Blueprint)
|
|
{
|
|
TSharedRef<FJsonObject> Root = MakeShared<FJsonObject>();
|
|
if (!Blueprint || !GEditor) { Root->SetStringField(TEXT("error"), TEXT("no editor or bp")); return JsonStringify(Root); }
|
|
UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
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<FBlueprintEditor*>(Inst);
|
|
FGraphPanelSelectionSet Sel = BPE->GetSelectedNodes();
|
|
|
|
TArray<TSharedPtr<FJsonValue>> Arr;
|
|
for (UObject* O : Sel)
|
|
{
|
|
if (UEdGraphNode* N = Cast<UEdGraphNode>(O))
|
|
{
|
|
Arr.Add(MakeShared<FJsonValueObject>(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<FString> UMCPGraphLibrary::ListOpenBlueprints()
|
|
{
|
|
TArray<FString> Out;
|
|
if (!GEditor) return Out;
|
|
UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
if (!AES) return Out;
|
|
TArray<UObject*> Assets = AES->GetAllEditedAssets();
|
|
for (UObject* A : Assets)
|
|
if (UBlueprint* BP = Cast<UBlueprint>(A))
|
|
Out.Add(BP->GetPathName());
|
|
return Out;
|
|
}
|
|
|
|
FString UMCPGraphLibrary::ResolveFunctionSourceJson(UEdGraph* Graph, const FString& NodeId)
|
|
{
|
|
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
|
UEdGraphNode* N = FindNodeByGuid(Graph, NodeId);
|
|
if (!N) { R->SetStringField(TEXT("error"), TEXT("node not found")); return JsonStringify(R); }
|
|
UK2Node_CallFunction* CF = Cast<UK2Node_CallFunction>(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<UBlueprint>(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<TSharedPtr<FJsonValue>>& Out)
|
|
{
|
|
TArray<UEdGraph*> AllGraphs;
|
|
BP->GetAllGraphs(AllGraphs);
|
|
for (UEdGraph* G : AllGraphs)
|
|
{
|
|
if (!G) continue;
|
|
for (UEdGraphNode* N : G->Nodes)
|
|
{
|
|
UK2Node_VariableGet* VG = Cast<UK2Node_VariableGet>(N);
|
|
UK2Node_VariableSet* VS = Cast<UK2Node_VariableSet>(N);
|
|
FName MemberName = NAME_None;
|
|
if (VG) MemberName = VG->VariableReference.GetMemberName();
|
|
else if (VS) MemberName = VS->VariableReference.GetMemberName();
|
|
if (MemberName == Name)
|
|
{
|
|
TSharedRef<FJsonObject> J = MakeShared<FJsonObject>();
|
|
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<FJsonValueObject>(J));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FString UMCPGraphLibrary::FindVariableReferencesJson(UBlueprint* Blueprint, FName VariableName)
|
|
{
|
|
TSharedRef<FJsonObject> R = MakeShared<FJsonObject>();
|
|
if (!Blueprint) { R->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(R); }
|
|
TArray<TSharedPtr<FJsonValue>> 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<FJsonObject> R = MakeShared<FJsonObject>();
|
|
if (!Blueprint) { R->SetStringField(TEXT("error"), TEXT("null bp")); return JsonStringify(R); }
|
|
TArray<UEdGraph*> AllGraphs;
|
|
Blueprint->GetAllGraphs(AllGraphs);
|
|
TArray<TSharedPtr<FJsonValue>> Refs;
|
|
for (UEdGraph* G : AllGraphs)
|
|
{
|
|
if (!G) continue;
|
|
for (UEdGraphNode* N : G->Nodes)
|
|
{
|
|
UK2Node_CallFunction* CF = Cast<UK2Node_CallFunction>(N);
|
|
if (!CF) continue;
|
|
if (CF->FunctionReference.GetMemberName() == FunctionName)
|
|
{
|
|
TSharedRef<FJsonObject> J = MakeShared<FJsonObject>();
|
|
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<FJsonValueObject>(J));
|
|
}
|
|
}
|
|
}
|
|
R->SetArrayField(TEXT("references"), Refs);
|
|
R->SetNumberField(TEXT("count"), Refs.Num());
|
|
return JsonStringify(R);
|
|
}
|
|
|
|
TArray<FString> UMCPGraphLibrary::FindBlueprintsByParent(const FString& ParentClassPath)
|
|
{
|
|
TArray<FString> Out;
|
|
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
|
|
FARFilter Filter;
|
|
Filter.ClassPaths.Add(FTopLevelAssetPath(TEXT("/Script/Engine"), TEXT("Blueprint")));
|
|
Filter.bRecursiveClasses = true;
|
|
TArray<FAssetData> Assets;
|
|
AR.GetAssets(Filter, Assets);
|
|
for (const FAssetData& AD : Assets)
|
|
{
|
|
const FString Parent = AD.GetTagValueRef<FString>(FBlueprintTags::ParentClassPath);
|
|
if (Parent == ParentClassPath || Parent.Contains(ParentClassPath))
|
|
Out.Add(AD.GetSoftObjectPath().ToString());
|
|
}
|
|
return Out;
|
|
}
|
|
|
|
TArray<FString> UMCPGraphLibrary::GetReferencedAssets(const FString& AssetPath)
|
|
{
|
|
TArray<FString> Out;
|
|
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
|
|
TArray<FName> Deps;
|
|
AR.GetDependencies(FName(*FPackageName::ObjectPathToPackageName(AssetPath)), Deps);
|
|
for (FName D : Deps) Out.Add(D.ToString());
|
|
return Out;
|
|
}
|
|
|
|
TArray<FString> UMCPGraphLibrary::GetReferencersOfAsset(const FString& AssetPath)
|
|
{
|
|
TArray<FString> Out;
|
|
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
|
|
TArray<FName> 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<FString>& NodeIds, const TArray<FVector2D>& 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<int32>(Positions[i].X);
|
|
N->NodePosY = static_cast<int32>(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<UEdGraphNode_Comment>(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<UAssetEditorSubsystem>();
|
|
return AES && AES->OpenEditorForAsset(Blueprint);
|
|
}
|
|
|
|
bool UMCPGraphLibrary::OpenGraphInEditor(UBlueprint* Blueprint, UEdGraph* Graph)
|
|
{
|
|
if (!OpenBlueprintEditor(Blueprint) || !Graph) return false;
|
|
UAssetEditorSubsystem* AES = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
|
|
IAssetEditorInstance* Inst = AES->FindEditorForAsset(Blueprint, true);
|
|
if (!Inst || Inst->GetEditorName() != FName(TEXT("BlueprintEditor"))) return false;
|
|
FBlueprintEditor* BPE = static_cast<FBlueprintEditor*>(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<UAssetEditorSubsystem>();
|
|
IAssetEditorInstance* Inst = AES->FindEditorForAsset(Blueprint, true);
|
|
FBlueprintEditor* BPE = static_cast<FBlueprintEditor*>(Inst);
|
|
BPE->JumpToNode(N, /*bRequestRename*/ false);
|
|
return true;
|
|
}
|