More blueprint handler work

This commit is contained in:
2026-03-19 12:37:36 -04:00
parent 9812a4a413
commit 3688a36682
9 changed files with 44 additions and 167 deletions

View File

@@ -27,13 +27,13 @@ public:
FString Blueprint;
UPROPERTY(meta=(Description="Component class name (e.g. StaticMeshComponent, SceneComponent)"))
FString ComponentClass;
FString Class;
UPROPERTY(meta=(Description="Component name for the new component"))
FString Component;
UPROPERTY(meta=(Optional, Description="Name of the parent component to attach to"))
FString ParentComponent;
FString Parent;
virtual FString GetDescription() const override
{
@@ -61,22 +61,22 @@ public:
return;
// Resolve the component class by name
UClass* ComponentClassObj = UWingTypes::TextToOneObjectType(ComponentClass);
UClass* ComponentClassObj = UWingTypes::TextToOneObjectType(Class);
if (!ComponentClassObj) return;
if (!ComponentClassObj->IsChildOf(UActorComponent::StaticClass()))
{
UWingServer::Printf(TEXT("ERROR: '%s' is not a subclass of UActorComponent\n"), *ComponentClass);
UWingServer::Printf(TEXT("ERROR: '%s' is not a subclass of UActorComponent\n"), *Class);
return;
}
// If parent component specified, find its SCS node
USCS_Node* ParentSCSNode = nullptr;
if (!ParentComponent.IsEmpty())
if (!Parent.IsEmpty())
{
for (USCS_Node* Node : Existing)
{
if (Node && Node->ComponentTemplate &&
WingUtils::Identifies(ParentComponent, Node->ComponentTemplate))
WingUtils::Identifies(Parent, Node->ComponentTemplate))
{
ParentSCSNode = Node;
break;
@@ -86,7 +86,7 @@ public:
if (!ParentSCSNode)
{
UWingServer::Printf(TEXT("ERROR: Parent component '%s' not found in Blueprint '%s'\n"),
*ParentComponent, *WingUtils::FormatName(BP));
*Parent, *WingUtils::FormatName(BP));
return;
}
}

View File

@@ -1,87 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Blueprint_RefreshAllNodes.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_RefreshAllNodes : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint package path"))
FString Blueprint;
virtual FString GetDescription() const override
{
return TEXT("Refresh all nodes in a Blueprint, removing orphaned pins. "
"Reports compiler warnings and errors.");
}
virtual void Handle() override
{
// Load Blueprint
WingFetcher F;
UBlueprint* BP = F.Asset(Blueprint).Cast<UBlueprint>();
if (!BP) return;
int32 GraphCount = WingUtils::AllGraphs(BP).Num();
int32 NodeCount = WingUtils::AllNodes(BP).Num();
// Refresh all nodes
FBlueprintEditorUtils::RefreshAllNodes(BP);
// Remove orphaned pins from all nodes
int32 OrphanedPinsRemoved = 0;
for (UEdGraphNode* Node : WingUtils::AllNodes(BP))
{
for (int32 i = Node->Pins.Num() - 1; i >= 0; --i)
{
UEdGraphPin* Pin = Node->Pins[i];
if (Pin && Pin->bOrphanedPin)
{
Pin->BreakAllPinLinks();
Node->Pins.RemoveAt(i);
OrphanedPinsRemoved++;
}
}
}
// Summary
UWingServer::Printf(TEXT("Refreshed %s: %d graphs, %d nodes"), *WingUtils::FormatName(BP), GraphCount, NodeCount);
if (OrphanedPinsRemoved > 0)
{
UWingServer::Printf(TEXT(", %d orphaned pins removed"), OrphanedPinsRemoved);
}
UWingServer::Print(TEXT("\n"));
// Collect compiler warnings and errors
if (BP->Status == BS_Error)
{
UWingServer::Print(TEXT("ERROR: Blueprint has compiler errors after refresh\n"));
}
for (UEdGraphNode* Node : WingUtils::AllNodes(BP))
{
if (!Node->bHasCompilerMessage) continue;
const TCHAR* Prefix = (Node->ErrorType == EMessageSeverity::Error) ? TEXT("ERROR") : TEXT("WARNING");
UWingServer::Printf(TEXT("%s: [%s] %s: %s\n"),
Prefix, *WingUtils::FormatName(Node->GetGraph()),
*WingUtils::FormatName(Node), *Node->ErrorMsg);
}
}
};

View File

@@ -3,11 +3,11 @@
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "WingServer.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "UObject/UObjectIterator.h"
#include "Blueprint_AddInterface.generated.h"
@@ -25,12 +25,11 @@ public:
FString Blueprint;
UPROPERTY(meta=(Description="Native UInterface class name or Blueprint Interface package path"))
FString InterfaceName;
FString Interface;
virtual FString GetDescription() const override
{
return TEXT("Add a Blueprint Interface implementation to a Blueprint. "
"Creates stub function graphs for each interface function.");
return TEXT("Add an interface to a blueprint");
}
virtual void Handle() override
@@ -40,7 +39,7 @@ public:
if (!BP) return;
// Resolve the interface class
UClass* InterfaceClass = FindInterfaceClass(InterfaceName);
UClass* InterfaceClass = UWingTypes::TextToOneInterfaceType(Interface);
if (!InterfaceClass) return;
// Check for duplicates
@@ -65,41 +64,14 @@ public:
// Collect stub function graph names from the newly added interface entry
UWingServer::Printf(TEXT("Added interface %s\n"), *WingUtils::FormatName(InterfaceClass));
UWingServer::Printf(TEXT("Function stubs:\n"));
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
{
if (IfaceDesc.Interface != InterfaceClass) continue;
for (const UEdGraph* Graph : IfaceDesc.Graphs)
{
if (Graph)
UWingServer::Printf(TEXT(" %s\n"), *WingUtils::FormatName(Graph));
UWingServer::Printf(TEXT("New Graph: %s\n"), *WingUtils::FormatName(Graph));
}
break;
}
}
private:
// Resolve an interface name to a UClass. Tries loaded UInterface classes
// first (for native interfaces), then falls back to loading a Blueprint
// Interface asset by package path.
static UClass* FindInterfaceClass(const FString& Name)
{
// Strategy 1: Search loaded UInterface classes by name
for (TObjectIterator<UClass> It; It; ++It)
{
if (!It->IsChildOf(UInterface::StaticClass())) continue;
if (WingUtils::Identifies(Name, *It))
return *It;
}
// Strategy 2: Try loading as a Blueprint Interface asset by package path
WingFetcher F;
UBlueprint* IfaceBP = F.Asset(Name).Cast<UBlueprint>();
if (IfaceBP && IfaceBP->GeneratedClass && IfaceBP->GeneratedClass->IsChildOf(UInterface::StaticClass()))
return IfaceBP->GeneratedClass;
UWingServer::Printf(TEXT("ERROR: Interface '%s' not found. Provide a native UInterface class name or Blueprint Interface package path.\n"),
*Name);
return nullptr;
}
};

View File

@@ -6,6 +6,7 @@
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraphNode.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Blueprint_Compile.generated.h"
@@ -20,35 +21,38 @@ class UWing_Blueprint_Compile : public UObject, public IWingHandler
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Path of the blueprint."))
UPROPERTY(meta=(Description="Blueprint to compile"))
FString Blueprint;
virtual FString GetDescription() const override
{
return TEXT("Compile a blueprint. ");
return TEXT("Compile a blueprint. "
"Reports compiler warnings and errors.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint *BP = F.Walk(Blueprint).Cast<UBlueprint>();
UBlueprint* BP = F.Asset(Blueprint).Cast<UBlueprint>();
if (!BP) return;
// Compile
EBlueprintCompileOptions CompileOpts =
EBlueprintCompileOptions::SkipSave |
EBlueprintCompileOptions::SkipGarbageCollection |
EBlueprintCompileOptions::SkipFiBSearchMetaUpdate;
EBlueprintCompileOptions::SkipSave |
EBlueprintCompileOptions::SkipGarbageCollection |
EBlueprintCompileOptions::SkipFiBSearchMetaUpdate;
FKismetEditorUtilities::CompileBlueprint(BP, CompileOpts, nullptr);
// Collect compiler messages from nodes
// Report compiler messages
for (UEdGraphNode* Node : WingUtils::AllNodes(BP))
{
if (!Node->bHasCompilerMessage) continue;
UWingServer::Printf(TEXT("%s %s: %s\n"),
*WingUtils::FormatName(Node->GetGraph()),
*WingUtils::FormatName(Node),
*Node->ErrorMsg);
const TCHAR* Prefix = (Node->ErrorType == EMessageSeverity::Error) ? TEXT("ERROR") : TEXT("WARNING");
UWingServer::Printf(TEXT("%s: [%s] %s: %s\n"),
Prefix, *WingUtils::FormatName(Node->GetGraph()),
*WingUtils::FormatName(Node), *Node->ErrorMsg);
}
UWingServer::Printf(TEXT("Compilation Done.\n"));
}
};

View File

@@ -61,8 +61,7 @@ public:
// Interfaces
for (const FBPInterfaceDescription& I : BP->ImplementedInterfaces)
{
if (I.Interface)
UWingServer::Printf(TEXT("Interface: %s\n"), *WingUtils::FormatName(I.Interface));
if (I.Interface) UWingServer::Printf(TEXT("Interface: %s\n"), *WingUtils::FormatName(I));
}
// Variables

View File

@@ -24,7 +24,7 @@ public:
FString Blueprint;
UPROPERTY(meta=(Description="Interface name to remove"))
FString InterfaceName;
FString Interface;
UPROPERTY(meta=(Optional, Description="If true, keep the function graphs as regular functions"))
bool PreserveFunctions = false;
@@ -42,27 +42,10 @@ public:
if (!BP) return;
// Find the interface by name
UClass* FoundInterface = nullptr;
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
{
if (!IfaceDesc.Interface) continue;
if (WingUtils::Identifies(InterfaceName, IfaceDesc.Interface))
{
FoundInterface = IfaceDesc.Interface;
break;
}
}
if (!FoundInterface)
{
UWingServer::Printf(TEXT("ERROR: Interface '%s' not found. Implemented interfaces:\n"), *InterfaceName);
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
{
if (IfaceDesc.Interface)
UWingServer::Printf(TEXT(" %s\n"), *WingUtils::FormatName(IfaceDesc.Interface));
}
return;
}
FBPInterfaceDescription *IFaceDesc =
WingUtils::FindExactlyOneNamed(Interface, BP->ImplementedInterfaces);
if (!IFaceDesc) return;
UClass* FoundInterface = IFaceDesc->Interface;
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions);

View File

@@ -40,8 +40,6 @@ public:
UBlueprint* BP = F.Asset(Blueprint).Cast<UBlueprint>();
if (!BP) return;
FString OldParentName = BP->ParentClass ? WingUtils::FormatName(BP->ParentClass) : TEXT("None");
// Find the new parent class by short type name
UClass* NewParentClassObj = UWingTypes::TextToOneObjectType(Parent);
if (!NewParentClassObj) return;
@@ -51,7 +49,7 @@ public:
FBlueprintEditorUtils::RefreshAllNodes(BP);
FKismetEditorUtilities::CompileBlueprint(BP);
UWingServer::Printf(TEXT("Reparented %s: %s -> %s\n"),
*WingUtils::FormatName(BP), *OldParentName, *WingUtils::FormatName(NewParentClassObj));
UWingServer::Printf(TEXT("Reparented %s -> %s\n"),
*WingUtils::FormatName(BP), *WingUtils::FormatName(NewParentClassObj));
}
};

View File

@@ -210,6 +210,11 @@ FString WingUtils::FormatName(const FUserPinInfo &Pin)
return SanitizeName(Pin.PinName);
}
FString WingUtils::FormatName(const FBPInterfaceDescription &IFace)
{
return FormatName(IFace.Interface);
}
// ============================================================
// Formatting other things
// ============================================================

View File

@@ -31,6 +31,8 @@ class USCS_Node;
struct FMemberReference;
struct FBPVariableDescription;
struct FUserPinInfo;
struct FBPInterfaceDescription;
// Stateless utility functions used by MCP handlers and the MCP server.
// This is effectively a namespace — all methods are static.
class WingUtils
@@ -73,6 +75,7 @@ public:
static FString FormatName(const UEnum *Enum);
static FString FormatName(const FProperty *Prop);
static FString FormatName(const FUserPinInfo &Pin);
static FString FormatName(const FBPInterfaceDescription &IFace);
////////////////////////////////////////////////////////
//