Add support for Blueprint Overrides, and make more progress on the hotkey widget.

This commit is contained in:
2026-04-10 16:56:17 -04:00
parent ad6a33582b
commit ea74f5fe76
9 changed files with 225 additions and 8 deletions

View File

@@ -0,0 +1,85 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingBasics.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraphSchema_K2.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintOverride_Add.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintOverride_Add : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta=(Description="Blueprint path"))
FString Blueprint;
UPROPERTY(EditAnywhere, meta=(Description="Function to override, in Class|Function format"))
FString Function;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Add a function override to a Blueprint."));
}
virtual void Handle() override
{
WingFetcher F(WingOut::Stdout);
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
// Find the function by matching against the menu string format (Class|Function)
TArray<UFunction*> Overridable = WingUtils::GetOverridableFunctions(BP);
UFunction* OverrideFunc = nullptr;
for (UFunction* Candidate : Overridable)
{
FString MenuString = WingUtils::GetFunctionMenuString(Candidate);
if (MenuString.Equals(Function, ESearchCase::IgnoreCase))
{
OverrideFunc = Candidate;
break;
}
}
if (!OverrideFunc)
{
WingOut::Stdout.Printf(TEXT("ERROR: '%s' is not an overridable function in this Blueprint.\n"), *Function);
return;
}
// Check if it already exists
FName FuncName = OverrideFunc->GetFName();
TSet<FName> ExistingNames;
FBlueprintEditorUtils::GetAllGraphNames(BP, ExistingNames);
if (ExistingNames.Contains(FuncName))
{
WingOut::Stdout.Printf(TEXT("ERROR: Override graph '%s' already exists.\n"), *Function);
return;
}
UClass* OverrideFuncClass = WingUtils::GetFunctionClass(OverrideFunc);
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, FuncName,
UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
if (!NewGraph)
{
WingOut::Stdout.Print(TEXT("ERROR: Failed to create graph.\n"));
return;
}
FBlueprintEditorUtils::AddFunctionGraph(BP, NewGraph, /*bIsUserCreated=*/false, OverrideFuncClass);
WingOut::Stdout.Printf(TEXT("Created override: %s\n"), *WingUtils::FormatName(NewGraph));
}
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingBasics.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintOverride_ShowMenu.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintOverride_ShowMenu : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, meta=(Description="Blueprint path"))
FString Blueprint;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Show the functions that can be overridden in a Blueprint."));
}
virtual void Handle() override
{
WingFetcher F(WingOut::Stdout);
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
TArray<UFunction*> Results = WingUtils::GetOverridableFunctions(BP);
for (UFunction *Func : Results)
{
UClass *Class = WingUtils::GetFunctionClass(Func);
WingOut::Stdout.Print(WingUtils::GetFunctionMenuString(Func));
WingOut::Stdout.Print(TEXT("\n"));
}
if (Results.IsEmpty())
WingOut::Stdout.Print(TEXT("No overridable functions.\n"));
}
};

View File

@@ -33,9 +33,10 @@ FName FWingGraphAction::GetSpawnerOwnerClass(const UBlueprintNodeSpawner* Spawne
if (const UBlueprintVariableNodeSpawner* VariableSpawner = Cast<UBlueprintVariableNodeSpawner>(Spawner))
{
if (VariableSpawner->IsLocalVariable()) return FName(TEXT("LocalVariable"));
return GetPropertyClassName(VariableSpawner->GetVarProperty());
}
if (const UBlueprintDelegateNodeSpawner* DelegateSpawner = Cast<UBlueprintDelegateNodeSpawner>(Spawner))
{
return GetPropertyClassName(DelegateSpawner->GetDelegateProperty());

View File

@@ -13,6 +13,10 @@
#include "WidgetBlueprint.h"
#include "Blueprint/WidgetTree.h"
// Blueprint override helpers
#include "EdGraphSchema_K2.h"
#include "ObjectEditorUtils.h"
// Reparent validation
#include "Engine/LevelScriptActor.h"
#include "Animation/AnimInstance.h"
@@ -403,6 +407,79 @@ bool WingUtils::CheckNewObjectNotNull(UObject *Obj, const TCHAR *Kind, WingOut E
return true;
}
// ============================================================
// Blueprint override helpers
// ============================================================
bool WingUtils::CanBlueprintOverride(UBlueprint* BP, const UFunction* Function)
{
if (!Function) return false;
if (!UEdGraphSchema_K2::CanKismetOverrideFunction(Function)) return false;
UClass* HiddenFromClass = BP->SkeletonGeneratedClass ? BP->SkeletonGeneratedClass->GetSuperClass() : BP->ParentClass.Get();
if (HiddenFromClass && FObjectEditorUtils::IsFunctionHiddenFromClass(Function, HiddenFromClass)) return false;
if (Function->HasAnyFunctionFlags(FUNC_Private)) return false;
if (!BP->AllowFunctionOverride(Function)) return false;
return true;
}
TArray<UFunction*> WingUtils::GetOverridableFunctions(UBlueprint *BP)
{
TArray<UFunction *> Candidates, Results;
TSet<UFunction*> Seen;
UClass* ParentClass = BP->SkeletonGeneratedClass ? BP->SkeletonGeneratedClass->GetSuperClass() : BP->ParentClass.Get();
for (TFieldIterator<UFunction> FunctionIt(ParentClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
Candidates.Add(*FunctionIt);
}
for (const FBPInterfaceDescription& InterfaceDesc : BP->ImplementedInterfaces)
{
UClass* InterfaceClass = InterfaceDesc.Interface.Get();
if (!InterfaceClass) continue;
for (TFieldIterator<UFunction> FunctionIt(InterfaceClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
Candidates.Add(*FunctionIt);
}
}
for (UClass* TempClass = BP->ParentClass.Get(); TempClass; TempClass = TempClass->GetSuperClass())
{
for (const FImplementedInterface& Interface : TempClass->Interfaces)
{
for (TFieldIterator<UFunction> FunctionIt(Interface.Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
Candidates.Add(*FunctionIt);
}
}
}
for (UFunction *Candidate : Candidates)
{
if (!Seen.Contains(Candidate))
{
Seen.Add(Candidate);
if (CanBlueprintOverride(BP, Candidate)) Results.Add(Candidate);
}
}
return Results;
}
UClass *WingUtils::GetFunctionClass(UFunction *Func)
{
UClass *Class = Func->GetOuterUClass();
UBlueprintGeneratedClass *BPGC = Cast<UBlueprintGeneratedClass>(Class);
if (BPGC) return BPGC->GetAuthoritativeClass();
else return Class;
}
FString WingUtils::GetFunctionMenuString(UFunction *Func)
{
UClass *Class = GetFunctionClass(Func);
TStringBuilder<256> Result;
Result.Append(FormatName(Class));
Result.AppendChar('|');
Result.Append(FormatName(Func));
return Result.ToString();
}
// ============================================================
// Reparent validation
// ============================================================

View File

@@ -280,6 +280,12 @@ public:
static FString GetHandlerName(UClass* HandlerClass);
static FString GetHandlerGroup(UClass* HandlerClass);
// ----- Blueprint override helpers -----
static bool CanBlueprintOverride(UBlueprint* BP, const UFunction* Function);
static TArray<UFunction*> GetOverridableFunctions(UBlueprint *BP);
static UClass *GetFunctionClass(UFunction *Func);
static FString GetFunctionMenuString(UFunction *Func);
// ----- Reparent validation -----
static bool CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed);