Blueprints switch from data-only to full when modified in that way.

This commit is contained in:
2026-03-19 22:15:04 -04:00
parent 9ad6515fb5
commit fae73e821d
5 changed files with 110 additions and 54 deletions

View File

@@ -0,0 +1,65 @@
#include "WingHacks.h"
#include "ToolMenuEntry.h"
#include "BlueprintEditor.h"
// ============================================================
// Private member access via template explicit-instantiation loophole.
//
// The C++ standard says "the usual access checking rules do not
// apply to names used to specify explicit instantiations." So
// &FToolMenuEntry::Action is legal as a template argument in an
// explicit instantiation, even though Action is private.
//
// The WingPrivateAccessor template captures the member pointer and exposes it
// through a friend function that we can call from normal code.
//
// See: https://bloglitb.blogspot.com/2011/12/access-to-private-members-safer.html
// ============================================================
template<typename Tag, typename Tag::type M>
struct WingPrivateAccessor
{
friend typename Tag::type GetPtr(Tag) { return M; }
};
// ----- FToolMenuEntry::Action -----
struct Tag_FToolMenuEntry_Action
{
using type = FToolUIActionChoice FToolMenuEntry::*;
friend type GetPtr(Tag_FToolMenuEntry_Action);
};
template struct WingPrivateAccessor<Tag_FToolMenuEntry_Action, &FToolMenuEntry::Action>;
const FToolUIActionChoice& WingHacks::GetAction(const FToolMenuEntry& Entry)
{
return Entry.*GetPtr(Tag_FToolMenuEntry_Action());
}
// ----- FToolMenuEntry::Command -----
struct Tag_FToolMenuEntry_Command
{
using type = TSharedPtr<const FUICommandInfo> FToolMenuEntry::*;
friend type GetPtr(Tag_FToolMenuEntry_Command);
};
template struct WingPrivateAccessor<Tag_FToolMenuEntry_Command, &FToolMenuEntry::Command>;
bool WingHacks::HasCommand(const FToolMenuEntry& Entry)
{
return Entry.*GetPtr(Tag_FToolMenuEntry_Command()) != nullptr;
}
// ----- FBlueprintEditor::bWasOpenedInDefaultsMode -----
struct Tag_FBlueprintEditor_DefaultsMode
{
using type = bool FBlueprintEditor::*;
friend type GetPtr(Tag_FBlueprintEditor_DefaultsMode);
};
template struct WingPrivateAccessor<Tag_FBlueprintEditor_DefaultsMode, &FBlueprintEditor::bWasOpenedInDefaultsMode>;
bool WingHacks::WasOpenedInDefaultsMode(FBlueprintEditor* Editor)
{
return Editor->*GetPtr(Tag_FBlueprintEditor_DefaultsMode());
}

View File

@@ -6,8 +6,11 @@
#include "Materials/Material.h"
#include "Materials/MaterialExpression.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintEditor.h"
#include "MaterialEditingLibrary.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Engine/Blueprint.h"
#include "WingHacks.h"
void FWingNotifier::AddTouchedObject(UObject* Obj)
{
@@ -20,6 +23,7 @@ void FWingNotifier::AddTouchedObject(UObject* Obj)
void FWingNotifier::SendNotifications()
{
UAssetEditorSubsystem* EditorSubsys = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
TSet<UEdGraphNode*> Nodes;
TSet<UEdGraph*> Graphs;
TSet<UMaterial*> Materials;
@@ -59,7 +63,19 @@ void FWingNotifier::SendNotifications()
for (UMaterial *Material : Materials)
UMaterialEditingLibrary::RebuildMaterialInstanceEditors(Material);
for (UBlueprint *Blueprint : Blueprints)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
IAssetEditorInstance *Editor = EditorSubsys->FindEditorForAsset(Blueprint, false);
if (Editor)
{
FBlueprintEditor* BPEditor = static_cast<FBlueprintEditor*>(Editor);
if (WingHacks::WasOpenedInDefaultsMode(BPEditor) && !FBlueprintEditorUtils::IsDataOnlyBlueprint(Blueprint))
{
EditorSubsys->CloseAllEditorsForAsset(Blueprint);
EditorSubsys->OpenEditorForAsset(Blueprint);
}
}
}
// FBlueprintEditorUtils::RefreshAllNodes(BP);
// FKismetEditorUtilities::CompileBlueprint(BP);

View File

@@ -1,4 +1,5 @@
#include "WingToolMenu.h"
#include "WingHacks.h"
#include "ToolMenuEntry.h"
#include "ToolMenuDelegates.h"
#include "ToolMenuContext.h"
@@ -8,54 +9,6 @@
#include "EdGraphSchema_K2.h"
#include "Framework/Commands/UIAction.h"
// ============================================================
// Private member access via template explicit-instantiation loophole.
//
// The C++ standard says "the usual access checking rules do not
// apply to names used to specify explicit instantiations." So
// &FToolMenuEntry::Action is legal as a template argument in an
// explicit instantiation, even though Action is private.
//
// The WingPrivateAccessor template captures the member pointer and exposes it
// through a friend function that we can call from normal code.
//
// See: https://bloglitb.blogspot.com/2011/12/access-to-private-members-safer.html
// ============================================================
template<typename Tag, typename Tag::type M>
struct WingPrivateAccessor
{
friend typename Tag::type GetPtr(Tag) { return M; }
};
// ----- FToolMenuEntry::Action -----
struct Tag_FToolMenuEntry_Action
{
using type = FToolUIActionChoice FToolMenuEntry::*;
friend type GetPtr(Tag_FToolMenuEntry_Action);
};
template struct WingPrivateAccessor<Tag_FToolMenuEntry_Action, &FToolMenuEntry::Action>;
static const FToolUIActionChoice& GetAction(const FToolMenuEntry& Entry)
{
return Entry.*GetPtr(Tag_FToolMenuEntry_Action());
}
// ----- FToolMenuEntry::Command -----
struct Tag_FToolMenuEntry_Command
{
using type = TSharedPtr<const FUICommandInfo> FToolMenuEntry::*;
friend type GetPtr(Tag_FToolMenuEntry_Command);
};
template struct WingPrivateAccessor<Tag_FToolMenuEntry_Command, &FToolMenuEntry::Command>;
static bool HasCommand(const FToolMenuEntry& Entry)
{
return Entry.*GetPtr(Tag_FToolMenuEntry_Command()) != nullptr;
}
// ============================================================
// Given a menu entry label, and a pin name (possibly empty),
// generate a better label for an LLM to type. The goal here
@@ -227,10 +180,10 @@ TArray<FToolMenuEntry> WingToolMenu::GetMenuItems(UEdGraphNode *Node, const FToo
bool WingToolMenu::CanExecute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
{
if (HasCommand(Entry))
if (WingHacks::HasCommand(Entry))
return false;
const FToolUIActionChoice& Choice = GetAction(Entry);
const FToolUIActionChoice& Choice = WingHacks::GetAction(Entry);
if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
{
@@ -254,10 +207,10 @@ bool WingToolMenu::CanExecute(const FToolMenuEntry& Entry, const FToolMenuContex
bool WingToolMenu::Execute(const FToolMenuEntry& Entry, const FToolMenuContext& Context)
{
if (HasCommand(Entry))
if (WingHacks::HasCommand(Entry))
return false;
const FToolUIActionChoice& Choice = GetAction(Entry);
const FToolUIActionChoice& Choice = WingHacks::GetAction(Entry);
if (const FToolUIAction* ToolAction = Choice.GetToolUIAction())
{

View File

@@ -0,0 +1,23 @@
#pragma once
#include "CoreMinimal.h"
struct FToolMenuEntry;
struct FToolUIActionChoice;
class FBlueprintEditor;
// Utility class that uses the C++ template explicit-instantiation
// loophole to access private members of engine classes.
// See WingHacks.cpp for details on the technique.
class WingHacks
{
public:
// Access FToolMenuEntry::Action (private member).
static const FToolUIActionChoice& GetAction(const FToolMenuEntry& Entry);
// Check whether FToolMenuEntry::Command (private member) is set.
static bool HasCommand(const FToolMenuEntry& Entry);
// Check FBlueprintEditor::bWasOpenedInDefaultsMode (private member).
static bool WasOpenedInDefaultsMode(FBlueprintEditor* Editor);
};

View File

@@ -10,8 +10,7 @@ class UEdGraphPin;
class UGraphNodeContextMenuContext;
// Utilities for manipulating UToolMenu structures.
// Uses the C++ template explicit-instantiation loophole to
// bypass access checks — see WingToolMenu.cpp for details.
// Uses WingHacks for private member access.
class WingToolMenu
{
public: