From f38630ea08ab50057e85dc4b05cd8a63779c87e9 Mon Sep 17 00:00:00 2001 From: jyelon Date: Sun, 22 Mar 2026 15:44:32 -0400 Subject: [PATCH] Code to make Blueprint_Reparent safer. --- Content/Testing/BP_Test.uasset | 4 +- Content/Testing/WB_Test.uasset | 3 ++ .../UEWingman/Handlers/Blueprint_Reparent.h | 8 ++++ .../Source/UEWingman/Private/WingUtils.cpp | 46 +++++++++++++++++++ .../Source/UEWingman/Public/WingUtils.h | 3 ++ .../Source/UEWingman/UEWingman.Build.cs | 3 +- 6 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 Content/Testing/WB_Test.uasset diff --git a/Content/Testing/BP_Test.uasset b/Content/Testing/BP_Test.uasset index 59787bfc..1902af27 100644 --- a/Content/Testing/BP_Test.uasset +++ b/Content/Testing/BP_Test.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a64c677d48ce708657273004e2e6ed3a831bc6cc5d83e36be0bd001f8082fbae -size 49752 +oid sha256:9a3fcb4a4ea0736637a4d0aff5198f4f0825d39475841c99ed3c470ac207140a +size 34231 diff --git a/Content/Testing/WB_Test.uasset b/Content/Testing/WB_Test.uasset new file mode 100644 index 00000000..06107175 --- /dev/null +++ b/Content/Testing/WB_Test.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:aa22b0edc55f4fd1bbf9a39dad11345b262b02b4c0c301851f2ab6bb3b744027 +size 22727 diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Reparent.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Reparent.h index 09ddaa8f..9c5f12c3 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Reparent.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Reparent.h @@ -44,6 +44,14 @@ public: UClass* NewParentClassObj = UWingTypes::TextToOneObjectType(Parent); if (!NewParentClassObj) return; + // Validate reparent + if (!WingUtils::CanReparentBlueprint(BP->GeneratedClass, NewParentClassObj)) + { + UWingServer::Printf(TEXT("Error: Cannot reparent %s to %s — incompatible class hierarchy.\n"), + *WingUtils::FormatName(BP), *WingUtils::FormatName(NewParentClassObj)); + return; + } + // Perform reparent BP->ParentClass = NewParentClassObj; FBlueprintEditorUtils::RefreshAllNodes(BP); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 30389aef..746132d6 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -22,6 +22,11 @@ #include "Misc/Paths.h" #include "Misc/PackageName.h" +// Reparent validation +#include "Engine/LevelScriptActor.h" +#include "Animation/AnimInstance.h" +#include "Blueprint/UserWidget.h" + // Animation Blueprint support #include "AnimStateNode.h" #include "AnimStateTransitionNode.h" @@ -397,6 +402,47 @@ bool WingUtils::CheckCanRename(UEdGraphNode* Node, const FString &Name) return true; } +// ============================================================ +// Reparent validation +// ============================================================ + +bool WingUtils::CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed) +{ + if (!CurrentGenerated || !Proposed) return false; + + UClass* CurrentParent = CurrentGenerated->GetSuperClass(); + if (!CurrentParent) return false; + + // Don't allow parenting to itself or one of its children + if (Proposed->IsChildOf(CurrentGenerated)) return false; + + // Don't allow parenting to an interface + if (Proposed->IsChildOf(UInterface::StaticClass())) return false; + + // LevelScriptActor blueprints stay in the LevelScriptActor hierarchy + if (CurrentParent->IsChildOf(ALevelScriptActor::StaticClass())) + return Proposed->IsChildOf(ALevelScriptActor::StaticClass()); + + // Actor blueprints stay in the Actor hierarchy, but not LevelScriptActor + if (CurrentParent->IsChildOf(AActor::StaticClass())) + return Proposed->IsChildOf(AActor::StaticClass()) && + !Proposed->IsChildOf(ALevelScriptActor::StaticClass()); + + // Component blueprints stay in the ActorComponent hierarchy + if (CurrentParent->IsChildOf(UActorComponent::StaticClass())) + return Proposed->IsChildOf(UActorComponent::StaticClass()); + + // Anim blueprints stay in the AnimInstance hierarchy + if (CurrentParent->IsChildOf(UAnimInstance::StaticClass())) + return Proposed->IsChildOf(UAnimInstance::StaticClass()); + + // Widget blueprints stay in the UserWidget hierarchy + if (CurrentParent->IsChildOf(UUserWidget::StaticClass())) + return Proposed->IsChildOf(UUserWidget::StaticClass()); + + return false; +} + // ============================================================ // Blueprint helpers // ============================================================ diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index 9d48b37b..144c7737 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -255,6 +255,9 @@ public: static FString GetHandlerGroup(UClass* HandlerClass); static void PrintHandlerHelp(UClass* HandlerClass); + // ----- Reparent validation ----- + static bool CanReparentBlueprint(UClass* CurrentGenerated, UClass* Proposed); + // ----- Common Error Reporting ----- static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name); static bool CheckExactlyNoneNamed(int Count, const TCHAR *Kind, const FString &Name); diff --git a/Plugins/UEWingman/Source/UEWingman/UEWingman.Build.cs b/Plugins/UEWingman/Source/UEWingman/UEWingman.Build.cs index 09839261..b42cdca5 100644 --- a/Plugins/UEWingman/Source/UEWingman/UEWingman.Build.cs +++ b/Plugins/UEWingman/Source/UEWingman/UEWingman.Build.cs @@ -32,7 +32,8 @@ public class UEWingman : ModuleRules "RHI", "Slate", "SlateCore", - "ToolMenus" + "ToolMenus", + "UMG" }); } }