From 9ad6515fb5030ed4105df1a170f8406e16e45bf8 Mon Sep 17 00:00:00 2001 From: jyelon Date: Thu, 19 Mar 2026 20:44:04 -0400 Subject: [PATCH] More work on blueprint component lists --- Content/Tangibles/TAN_Character.uasset | 4 +- Content/Testing/BP1.uasset | 3 + .../Handlers/BlueprintComponent_Add.h | 35 ++--- .../Handlers/BlueprintComponent_Reparent.h | 25 ++- .../UEWingman/Handlers/Blueprint_Dump.h | 2 +- .../UEWingman/Private/WingActorComponent.cpp | 146 ++++++++++++------ .../Source/UEWingman/Private/WingFetcher.cpp | 19 +-- .../Source/UEWingman/Private/WingNotifier.cpp | 1 + .../UEWingman/Public/WingActorComponent.h | 19 ++- 9 files changed, 150 insertions(+), 104 deletions(-) create mode 100644 Content/Testing/BP1.uasset diff --git a/Content/Tangibles/TAN_Character.uasset b/Content/Tangibles/TAN_Character.uasset index 83ec658d..4cad3f19 100644 --- a/Content/Tangibles/TAN_Character.uasset +++ b/Content/Tangibles/TAN_Character.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29b2e6afcce8a0d56e35b5fb1e3c9bcf91598766d5ed765561b8f45311972cca -size 372409 +oid sha256:30e797a1ddbefeca26d9fe9c2fe5e2bc6e528b97aa80f62746b107325fad90e9 +size 372386 diff --git a/Content/Testing/BP1.uasset b/Content/Testing/BP1.uasset new file mode 100644 index 00000000..d485839a --- /dev/null +++ b/Content/Testing/BP1.uasset @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:023eaf937ba6b115dd7f52faacac99d31e9f334bb040fbc6b2de27ab3086096f +size 24131 diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Add.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Add.h index 9dce477f..4819a9b1 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Add.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Add.h @@ -33,7 +33,7 @@ public: UPROPERTY(meta=(Description="Component name for the new component")) FString Component; - UPROPERTY(meta=(Optional, Description="Name of the parent component to attach to")) + UPROPERTY(meta=(Description="Name of the parent component to attach to")) FString Parent; virtual FString GetDescription() const override @@ -62,39 +62,26 @@ public: return; // Resolve the component class by name - UClass* ComponentClassObj = UWingTypes::TextToOneObjectType(Class); - if (!ComponentClassObj) return; - if (!ComponentClassObj->IsChildOf(UActorComponent::StaticClass())) - { - UWingServer::Printf(TEXT("ERROR: '%s' is not a subclass of UActorComponent\n"), *Class); - return; - } + UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class); + if (!ComponentClass) return; - // If parent component specified, find it among all components - FWingActorComponent* ParentComp = nullptr; - if (!Parent.IsEmpty()) - { - ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents); - if (!ParentComp) return; - } + // Find the specified parent component + FWingActorComponent* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents); + if (!ParentComp) return; // Create the SCS node - USCS_Node* NewNode = SCS->CreateNode(ComponentClassObj, FName(*WingUtils::UnsanitizeName(Component))); + FString NewName = WingUtils::UnsanitizeName(Component); + USCS_Node *NewNode = FWingActorComponent::AddComponent(BP, ComponentClass, ParentComp, NewName); if (!NewNode) { UWingServer::Printf(TEXT("ERROR: Failed to create SCS node for component '%s' with class '%s'\n"), - *Component, *WingUtils::FormatName(ComponentClassObj)); + *Component, *WingUtils::FormatName(ComponentClass)); return; } - // Add to the hierarchy - if (!FWingActorComponent::SetParent(NewNode, ParentComp)) - return; - SCS->AddNode(NewNode); - UWingServer::Printf(TEXT("Added component %s (%s)"), - *WingUtils::FormatName(NewNode->ComponentTemplate), - *WingUtils::FormatName(ComponentClassObj)); + *WingUtils::FormatName(NewNode), + *WingUtils::FormatName(ComponentClass)); if (ParentComp) UWingServer::Printf(TEXT(" under %s"), *ParentComp->GetName()); UWingServer::Print(TEXT("\n")); diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Reparent.h b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Reparent.h index 5af3a789..7d1acdfc 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Reparent.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/BlueprintComponent_Reparent.h @@ -25,7 +25,7 @@ public: UPROPERTY(meta=(Description="Path to the component (e.g. '/Game/MyBP,component:MyComp')")) FString Component; - UPROPERTY(meta=(Optional, Description="New parent component name. If empty, detaches to root.")) + UPROPERTY(meta=(Description="New parent component name.")) FString Parent; virtual FString GetDescription() const override @@ -40,25 +40,18 @@ public: if (!Node) return; USimpleConstructionScript* SCS = Node->GetSCS(); - if (!SCS) return; // Find the new parent among all components (if specified) - FWingActorComponent* NewParent = nullptr; - TArray AllComponents; - if (!Parent.IsEmpty()) - { - UBlueprint* BP = SCS->GetBlueprint(); - AllComponents = FWingActorComponent::GetAll(BP); - NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents); - if (!NewParent) return; - } + UBlueprint *BP = SCS->GetBlueprint(); + TArray AllComponents = FWingActorComponent::GetAll(BP); + FWingActorComponent* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents); + if (!NewParent) return; - if (!FWingActorComponent::SetParent(Node, NewParent)) + if (!FWingActorComponent::ReparentComponent(BP, + Node, NewParent)) return; - if (NewParent) - UWingServer::Printf(TEXT("Reparented %s under %s.\n"), *WingUtils::FormatName(Node), *NewParent->GetName()); - else - UWingServer::Printf(TEXT("Detached %s to root.\n"), *WingUtils::FormatName(Node)); + UWingServer::Printf(TEXT("Reparented %s under %s.\n"), + *WingUtils::FormatName(Node), *WingUtils::FormatName(*NewParent)); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h index 99d40338..3f874801 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Blueprint_Dump.h @@ -85,7 +85,7 @@ public: UWingServer::Print(TEXT("\nComponents:\n")); for (const FWingActorComponent& Comp : Components) { - UWingServer::Printf(TEXT(" %s %s"), *Comp.GetClassName(), *Comp.GetName()); + UWingServer::Printf(TEXT(" %s %s"), *Comp.GetClassName(), *WingUtils::FormatName(Comp)); FString ParentName = Comp.GetParentName(); if (!ParentName.IsEmpty()) UWingServer::Printf(TEXT(" [parent: %s]"), *ParentName); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp index 5d4ca596..a5a54021 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingActorComponent.cpp @@ -9,6 +9,9 @@ #include "GameFramework/Actor.h" #include "Kismet2/BlueprintEditorUtils.h" +FWingActorComponent::FWingActorComponent(USCS_Node *Node) : + SCSNode(Node), Owner(Node->GetSCS()->GetBlueprint()->GeneratedClass) {} + FString FWingActorComponent::GetName() const { if (SCSNode) return WingUtils::FormatName(SCSNode); @@ -48,56 +51,112 @@ bool FWingActorComponent::IsOwnedBy(const UBlueprint* BP) const return BP && BP->GeneratedClass == Owner; } -bool FWingActorComponent::SetParent(USCS_Node* ChildNode, const FWingActorComponent* Parent) +bool FWingActorComponent::CheckEditableComponent(UBlueprint *BP, const FWingActorComponent *Component) { - // Validate before modifying anything - if (Parent) + if ((!Component) || (Component->IsEmpty())) { - if (Parent->SCSNode) - { - // Check for cycles: walk up from parent to make sure we don't reach the child - USCS_Node* Ancestor = Parent->SCSNode; - while (Ancestor) - { - if (Ancestor == ChildNode) - { - UWingServer::Printf(TEXT("ERROR: Cannot reparent — would create a cycle.\n")); - return false; - } - Ancestor = Ancestor->GetSCS()->FindParentNode(Ancestor); - } - } - else - { - USceneComponent* NativeScene = Cast(Parent->NativeComponent); - if (!NativeScene) - { - UWingServer::Printf(TEXT("ERROR: Native parent '%s' is not a SceneComponent — cannot attach to it.\n"), - *Parent->GetName()); - return false; - } - } + UWingServer::Printf(TEXT("Component does not exist.\n")); + return false; } - - // Clear any existing parent attachment - ChildNode->Modify(); - ChildNode->bIsParentComponentNative = false; - ChildNode->ParentComponentOrVariableName = NAME_None; - ChildNode->ParentComponentOwnerClassName = NAME_None; - - if (!Parent) - return true; - - if (Parent->SCSNode) + if (Component->IsNative()) { - ChildNode->SetParent(Parent->SCSNode); - return true; + UWingServer::Printf(TEXT("Cannot edit native components.\n")); + return false; + } + if (!Component->IsOwnedBy(BP)) + { + UWingServer::Printf(TEXT("Component %s is inherited, to edit it, you must edit BP %s\n"), + *WingUtils::FormatName(*Component), *WingUtils::FormatName(Component->Owner)); + return false; } - - ChildNode->SetParent(Cast(Parent->NativeComponent)); return true; } +bool FWingActorComponent::CheckValidParent(const FWingActorComponent *Parent) +{ + if ((!Parent) || (Parent->IsEmpty())) + { + UWingServer::Printf(TEXT("Cannot create a component without a parent.\n")); + return false; + } + if (Parent->IsNative() && (!Parent->NativeComponent->GetClass()->IsChildOf(USceneComponent::StaticClass()))) + { + UWingServer::Printf(TEXT("Native component %s is not a scene component, cannot be a parent."), + *WingUtils::FormatName(Parent->NativeComponent)); + return false; + } + return true; +} + +bool FWingActorComponent::CheckValidComponentClass(UClass *Class) +{ + if (!Class->IsChildOf(UActorComponent::StaticClass())) + { + UWingServer::Printf(TEXT("Cannot create a component of class %s, which is not an actor component.\n"), + *WingUtils::FormatName(Class)); + return false; + } + return true; +} + +void FWingActorComponent::AddNodeInternal(UBlueprint *BP, USCS_Node *NewNode, const FWingActorComponent *Parent) +{ + if (Parent->SCSNode && Parent->IsOwnedBy(BP)) + { + Parent->SCSNode->AddChildNode(NewNode, true); + } + else if (Parent->SCSNode) + { + NewNode->SetParent(Parent->SCSNode); + BP->SimpleConstructionScript->AddNode(NewNode); + } + else + { + NewNode->SetParent(Cast(Parent->NativeComponent)); + BP->SimpleConstructionScript->AddNode(NewNode); + } +} + +USCS_Node *FWingActorComponent::AddComponent(UBlueprint *BP, UClass *Class, const FWingActorComponent* Parent, const FString &Name) +{ + if (!CheckValidParent(Parent)) return nullptr; + if (!CheckValidComponentClass(Class)) return nullptr; + + USCS_Node *NewNode = BP->SimpleConstructionScript->CreateNode(Class, FName(*Name)); + if (NewNode == nullptr) + { + UWingServer::Printf(TEXT("Could not create new component %s of class %s, unknown reason.\n"), + *Name, *WingUtils::FormatName(Class)); + return nullptr; + } + + AddNodeInternal(BP, NewNode, Parent); + return NewNode; +} + +bool FWingActorComponent::ReparentComponent(UBlueprint *BP, USCS_Node *SCSNode, const FWingActorComponent* Parent) +{ + FWingActorComponent Component(SCSNode); + if (!CheckEditableComponent(BP, &Component)) return false; + if (!CheckValidParent(Parent)) return false; + + if (Parent->SCSNode == SCSNode) + { + UWingServer::Printf(TEXT("You may not parent a component to itself.\n")); + return false; + } + if (Parent->SCSNode && Parent->SCSNode->IsChildOf(SCSNode)) + { + UWingServer::Printf(TEXT("You may not parent a component to one of its own children.\n")); + return false; + } + + BP->SimpleConstructionScript->RemoveNode(SCSNode); + AddNodeInternal(BP, SCSNode, Parent); + return true; +} + + TArray FWingActorComponent::GetAll(UBlueprint* BP) { TArray Result; @@ -123,9 +182,8 @@ TArray FWingActorComponent::GetAll(UBlueprint* BP) { UBlueprint* WalkBP = Hierarchy[i]; if (!WalkBP->SimpleConstructionScript) continue; - UClass* OwnerClass = WalkBP->GeneratedClass; for (USCS_Node* Node : WalkBP->SimpleConstructionScript->GetAllNodes()) - Result.Emplace(Node, OwnerClass); + Result.Emplace(Node); } return Result; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp index 267e25c6..cb072546 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingFetcher.cpp @@ -115,20 +115,15 @@ WingFetcher& WingFetcher::Asset(const FString& PackagePath) { if (bError) return *this; - // Try to find the object in memory first (silent), then load if needed. - // LoadObject logs its own errors when the package doesn't exist, so - // we check DoesPackageExist first to avoid redundant log spam. - SetObj(FindObject(nullptr, *PackagePath)); - if (!Obj) + // Check if the package exists before calling LoadObject, because + // LoadObject logs its own errors when the package doesn't exist. + FString PackageName = FPackageName::ObjectPathToPackageName(PackagePath); + if (!FPackageName::DoesPackageExist(PackageName)) { - FString PackageName = FPackageName::ObjectPathToPackageName(PackagePath); - if (!FPackageName::DoesPackageExist(PackageName)) - { - UWingServer::Printf(TEXT("ERROR: Asset '%s' does not exist.\n"), *PackagePath); - return SetError(); - } - SetObj(LoadObject(nullptr, *PackagePath)); + UWingServer::Printf(TEXT("ERROR: Asset '%s' does not exist.\n"), *PackagePath); + return SetError(); } + SetObj(LoadObject(nullptr, *PackagePath)); if (!Obj) { UWingServer::Printf(TEXT("ERROR: Could not load asset '%s'\n"), *PackagePath); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp index e8cb3cf6..fdc8462d 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp @@ -7,6 +7,7 @@ #include "Materials/MaterialExpression.h" #include "Kismet2/BlueprintEditorUtils.h" #include "MaterialEditingLibrary.h" +#include "Subsystems/AssetEditorSubsystem.h" void FWingNotifier::AddTouchedObject(UObject* Obj) { diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h b/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h index b1253f75..85799521 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h @@ -16,7 +16,7 @@ struct FWingActorComponent UClass* Owner = nullptr; FWingActorComponent() = default; - FWingActorComponent(USCS_Node* InSCSNode, UClass* InOwner) : SCSNode(InSCSNode), Owner(InOwner) {} + FWingActorComponent(USCS_Node* Node); FWingActorComponent(UActorComponent* InNative, UClass* InOwner) : NativeComponent(InNative), Owner(InOwner) {} explicit operator bool() const { return SCSNode || NativeComponent; } @@ -24,15 +24,24 @@ struct FWingActorComponent FString GetName() const; FString GetClassName() const; FString GetParentName() const; + bool IsEmpty() const { return SCSNode == nullptr && NativeComponent == nullptr; } bool IsNative() const { return NativeComponent != nullptr; } bool IsOwnedBy(const UBlueprint* BP) const; - // Attach ChildNode under this component. If this is an SCS node, uses - // AddChildNode. If native, uses SetParent with the native scene component. - // Returns false and prints an error if attachment is not possible. - static bool SetParent(USCS_Node* ChildNode, const FWingActorComponent* Parent); + // Add a new SCS node, underneath the existing parent. + static USCS_Node *AddComponent(UBlueprint *BP, UClass *Class, const FWingActorComponent* Parent, const FString &Name); + + // Reparent an existing SCS node to a specified parent. + static bool ReparentComponent(UBlueprint *BP, USCS_Node *Component, const FWingActorComponent *Parent); + // Collect all components: native from CDO first, then SCS nodes // walking the hierarchy from oldest ancestor to current blueprint. static TArray GetAll(UBlueprint* BP); + +private: + static bool CheckEditableComponent(UBlueprint *BP, const FWingActorComponent *Component); + static bool CheckValidParent(const FWingActorComponent *Component); + static bool CheckValidComponentClass(UClass *Class); + static void AddNodeInternal(UBlueprint *BP, USCS_Node *NewNode, const FWingActorComponent *Parent); };