From fae73e821d7028c6337ee61f9aa6153f012fdd25 Mon Sep 17 00:00:00 2001 From: jyelon Date: Thu, 19 Mar 2026 22:15:04 -0400 Subject: [PATCH] Blueprints switch from data-only to full when modified in that way. --- .../Source/UEWingman/Private/WingHacks.cpp | 65 +++++++++++++++++++ .../Source/UEWingman/Private/WingNotifier.cpp | 16 +++++ .../Source/UEWingman/Private/WingToolMenu.cpp | 57 ++-------------- .../Source/UEWingman/Public/WingHacks.h | 23 +++++++ .../Source/UEWingman/Public/WingToolMenu.h | 3 +- 5 files changed, 110 insertions(+), 54 deletions(-) create mode 100644 Plugins/UEWingman/Source/UEWingman/Private/WingHacks.cpp create mode 100644 Plugins/UEWingman/Source/UEWingman/Public/WingHacks.h diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingHacks.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingHacks.cpp new file mode 100644 index 00000000..2a5da5e1 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingHacks.cpp @@ -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 +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; + +const FToolUIActionChoice& WingHacks::GetAction(const FToolMenuEntry& Entry) +{ + return Entry.*GetPtr(Tag_FToolMenuEntry_Action()); +} + +// ----- FToolMenuEntry::Command ----- + +struct Tag_FToolMenuEntry_Command +{ + using type = TSharedPtr FToolMenuEntry::*; + friend type GetPtr(Tag_FToolMenuEntry_Command); +}; +template struct WingPrivateAccessor; + +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; + +bool WingHacks::WasOpenedInDefaultsMode(FBlueprintEditor* Editor) +{ + return Editor->*GetPtr(Tag_FBlueprintEditor_DefaultsMode()); +} diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp index fdc8462d..66bf9ef2 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp @@ -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(); TSet Nodes; TSet Graphs; TSet 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(Editor); + if (WingHacks::WasOpenedInDefaultsMode(BPEditor) && !FBlueprintEditorUtils::IsDataOnlyBlueprint(Blueprint)) + { + EditorSubsys->CloseAllEditorsForAsset(Blueprint); + EditorSubsys->OpenEditorForAsset(Blueprint); + } + } + } // FBlueprintEditorUtils::RefreshAllNodes(BP); // FKismetEditorUtilities::CompileBlueprint(BP); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingToolMenu.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingToolMenu.cpp index 606e5232..826c49d5 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingToolMenu.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingToolMenu.cpp @@ -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 -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; - -static const FToolUIActionChoice& GetAction(const FToolMenuEntry& Entry) -{ - return Entry.*GetPtr(Tag_FToolMenuEntry_Action()); -} - -// ----- FToolMenuEntry::Command ----- - -struct Tag_FToolMenuEntry_Command -{ - using type = TSharedPtr FToolMenuEntry::*; - friend type GetPtr(Tag_FToolMenuEntry_Command); -}; -template struct WingPrivateAccessor; - -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 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()) { diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingHacks.h b/Plugins/UEWingman/Source/UEWingman/Public/WingHacks.h new file mode 100644 index 00000000..3c2b6855 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingHacks.h @@ -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); +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingToolMenu.h b/Plugins/UEWingman/Source/UEWingman/Public/WingToolMenu.h index d2efb1b3..b4446969 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingToolMenu.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingToolMenu.h @@ -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: