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; FString Blueprint;
UPROPERTY(meta=(Description="Component class name (e.g. StaticMeshComponent, SceneComponent)")) UPROPERTY(meta=(Description="Component class name (e.g. StaticMeshComponent, SceneComponent)"))
FString ComponentClass; FString Class;
UPROPERTY(meta=(Description="Component name for the new component")) UPROPERTY(meta=(Description="Component name for the new component"))
FString Component; FString Component;
UPROPERTY(meta=(Optional, Description="Name of the parent component to attach to")) UPROPERTY(meta=(Optional, Description="Name of the parent component to attach to"))
FString ParentComponent; FString Parent;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
@@ -61,22 +61,22 @@ public:
return; return;
// Resolve the component class by name // Resolve the component class by name
UClass* ComponentClassObj = UWingTypes::TextToOneObjectType(ComponentClass); UClass* ComponentClassObj = UWingTypes::TextToOneObjectType(Class);
if (!ComponentClassObj) return; if (!ComponentClassObj) return;
if (!ComponentClassObj->IsChildOf(UActorComponent::StaticClass())) 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; return;
} }
// If parent component specified, find its SCS node // If parent component specified, find its SCS node
USCS_Node* ParentSCSNode = nullptr; USCS_Node* ParentSCSNode = nullptr;
if (!ParentComponent.IsEmpty()) if (!Parent.IsEmpty())
{ {
for (USCS_Node* Node : Existing) for (USCS_Node* Node : Existing)
{ {
if (Node && Node->ComponentTemplate && if (Node && Node->ComponentTemplate &&
WingUtils::Identifies(ParentComponent, Node->ComponentTemplate)) WingUtils::Identifies(Parent, Node->ComponentTemplate))
{ {
ParentSCSNode = Node; ParentSCSNode = Node;
break; break;
@@ -86,7 +86,7 @@ public:
if (!ParentSCSNode) if (!ParentSCSNode)
{ {
UWingServer::Printf(TEXT("ERROR: Parent component '%s' not found in Blueprint '%s'\n"), UWingServer::Printf(TEXT("ERROR: Parent component '%s' not found in Blueprint '%s'\n"),
*ParentComponent, *WingUtils::FormatName(BP)); *Parent, *WingUtils::FormatName(BP));
return; 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 "CoreMinimal.h"
#include "WingHandler.h" #include "WingHandler.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingTypes.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "WingServer.h" #include "WingServer.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "UObject/UObjectIterator.h"
#include "Blueprint_AddInterface.generated.h" #include "Blueprint_AddInterface.generated.h"
@@ -25,12 +25,11 @@ public:
FString Blueprint; FString Blueprint;
UPROPERTY(meta=(Description="Native UInterface class name or Blueprint Interface package path")) UPROPERTY(meta=(Description="Native UInterface class name or Blueprint Interface package path"))
FString InterfaceName; FString Interface;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("Add a Blueprint Interface implementation to a Blueprint. " return TEXT("Add an interface to a blueprint");
"Creates stub function graphs for each interface function.");
} }
virtual void Handle() override virtual void Handle() override
@@ -40,7 +39,7 @@ public:
if (!BP) return; if (!BP) return;
// Resolve the interface class // Resolve the interface class
UClass* InterfaceClass = FindInterfaceClass(InterfaceName); UClass* InterfaceClass = UWingTypes::TextToOneInterfaceType(Interface);
if (!InterfaceClass) return; if (!InterfaceClass) return;
// Check for duplicates // Check for duplicates
@@ -65,41 +64,14 @@ public:
// Collect stub function graph names from the newly added interface entry // Collect stub function graph names from the newly added interface entry
UWingServer::Printf(TEXT("Added interface %s\n"), *WingUtils::FormatName(InterfaceClass)); UWingServer::Printf(TEXT("Added interface %s\n"), *WingUtils::FormatName(InterfaceClass));
UWingServer::Printf(TEXT("Function stubs:\n"));
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces) for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
{ {
if (IfaceDesc.Interface != InterfaceClass) continue; if (IfaceDesc.Interface != InterfaceClass) continue;
for (const UEdGraph* Graph : IfaceDesc.Graphs) for (const UEdGraph* Graph : IfaceDesc.Graphs)
{ {
if (Graph) UWingServer::Printf(TEXT("New Graph: %s\n"), *WingUtils::FormatName(Graph));
UWingServer::Printf(TEXT(" %s\n"), *WingUtils::FormatName(Graph));
} }
break; 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 "WingFetcher.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "EdGraph/EdGraphNode.h"
#include "Kismet2/KismetEditorUtilities.h" #include "Kismet2/KismetEditorUtilities.h"
#include "Blueprint_Compile.generated.h" #include "Blueprint_Compile.generated.h"
@@ -20,35 +21,38 @@ class UWing_Blueprint_Compile : public UObject, public IWingHandler
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Optional, Description="Path of the blueprint.")) UPROPERTY(meta=(Description="Blueprint to compile"))
FString Blueprint; FString Blueprint;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("Compile a blueprint. "); return TEXT("Compile a blueprint. "
"Reports compiler warnings and errors.");
} }
virtual void Handle() override virtual void Handle() override
{ {
WingFetcher F; WingFetcher F;
UBlueprint *BP = F.Walk(Blueprint).Cast<UBlueprint>(); UBlueprint* BP = F.Asset(Blueprint).Cast<UBlueprint>();
if (!BP) return;
// Compile
EBlueprintCompileOptions CompileOpts = EBlueprintCompileOptions CompileOpts =
EBlueprintCompileOptions::SkipSave | EBlueprintCompileOptions::SkipSave |
EBlueprintCompileOptions::SkipGarbageCollection | EBlueprintCompileOptions::SkipGarbageCollection |
EBlueprintCompileOptions::SkipFiBSearchMetaUpdate; EBlueprintCompileOptions::SkipFiBSearchMetaUpdate;
FKismetEditorUtilities::CompileBlueprint(BP, CompileOpts, nullptr); FKismetEditorUtilities::CompileBlueprint(BP, CompileOpts, nullptr);
// Collect compiler messages from nodes // Report compiler messages
for (UEdGraphNode* Node : WingUtils::AllNodes(BP)) for (UEdGraphNode* Node : WingUtils::AllNodes(BP))
{ {
if (!Node->bHasCompilerMessage) continue; if (!Node->bHasCompilerMessage) continue;
UWingServer::Printf(TEXT("%s %s: %s\n"), const TCHAR* Prefix = (Node->ErrorType == EMessageSeverity::Error) ? TEXT("ERROR") : TEXT("WARNING");
*WingUtils::FormatName(Node->GetGraph()), UWingServer::Printf(TEXT("%s: [%s] %s: %s\n"),
*WingUtils::FormatName(Node), Prefix, *WingUtils::FormatName(Node->GetGraph()),
*Node->ErrorMsg); *WingUtils::FormatName(Node), *Node->ErrorMsg);
} }
UWingServer::Printf(TEXT("Compilation Done.\n")); UWingServer::Printf(TEXT("Compilation Done.\n"));
} }
}; };

View File

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

View File

@@ -24,7 +24,7 @@ public:
FString Blueprint; FString Blueprint;
UPROPERTY(meta=(Description="Interface name to remove")) UPROPERTY(meta=(Description="Interface name to remove"))
FString InterfaceName; FString Interface;
UPROPERTY(meta=(Optional, Description="If true, keep the function graphs as regular functions")) UPROPERTY(meta=(Optional, Description="If true, keep the function graphs as regular functions"))
bool PreserveFunctions = false; bool PreserveFunctions = false;
@@ -42,27 +42,10 @@ public:
if (!BP) return; if (!BP) return;
// Find the interface by name // Find the interface by name
UClass* FoundInterface = nullptr; FBPInterfaceDescription *IFaceDesc =
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces) WingUtils::FindExactlyOneNamed(Interface, BP->ImplementedInterfaces);
{ if (!IFaceDesc) return;
if (!IfaceDesc.Interface) continue; UClass* FoundInterface = IFaceDesc->Interface;
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;
}
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName(); FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions); FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions);

View File

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

View File

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

View File

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