More work on blueprint component lists

This commit is contained in:
2026-03-19 20:44:04 -04:00
parent 2eb2be7af1
commit 9ad6515fb5
9 changed files with 150 additions and 104 deletions

Binary file not shown.

BIN
Content/Testing/BP1.uasset LFS Normal file

Binary file not shown.

View File

@@ -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);
// 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"));

View File

@@ -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<FWingActorComponent> AllComponents;
if (!Parent.IsEmpty())
{
UBlueprint* BP = SCS->GetBlueprint();
AllComponents = FWingActorComponent::GetAll(BP);
NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents);
UBlueprint *BP = SCS->GetBlueprint();
TArray<FWingActorComponent> 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));
}
};

View File

@@ -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);

View File

@@ -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"));
UWingServer::Printf(TEXT("Component does not exist.\n"));
return false;
}
Ancestor = Ancestor->GetSCS()->FindParentNode(Ancestor);
if (Component->IsNative())
{
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;
}
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
{
USceneComponent* NativeScene = Cast<USceneComponent>(Parent->NativeComponent);
if (!NativeScene)
NewNode->SetParent(Cast<USceneComponent>(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("ERROR: Native parent '%s' is not a SceneComponent — cannot attach to it.\n"),
*Parent->GetName());
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;
}
}
}
// 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 (Parent->SCSNode && Parent->SCSNode->IsChildOf(SCSNode))
{
ChildNode->SetParent(Parent->SCSNode);
return true;
UWingServer::Printf(TEXT("You may not parent a component to one of its own children.\n"));
return false;
}
ChildNode->SetParent(Cast<USceneComponent>(Parent->NativeComponent));
BP->SimpleConstructionScript->RemoveNode(SCSNode);
AddNodeInternal(BP, SCSNode, Parent);
return true;
}
TArray<FWingActorComponent> FWingActorComponent::GetAll(UBlueprint* BP)
{
TArray<FWingActorComponent> Result;
@@ -123,9 +182,8 @@ TArray<FWingActorComponent> 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;

View File

@@ -115,12 +115,8 @@ 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<UObject>(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))
{
@@ -128,7 +124,6 @@ WingFetcher& WingFetcher::Asset(const FString& PackagePath)
return SetError();
}
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
}
if (!Obj)
{
UWingServer::Printf(TEXT("ERROR: Could not load asset '%s'\n"), *PackagePath);

View File

@@ -7,6 +7,7 @@
#include "Materials/MaterialExpression.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "MaterialEditingLibrary.h"
#include "Subsystems/AssetEditorSubsystem.h"
void FWingNotifier::AddTouchedObject(UObject* Obj)
{

View File

@@ -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<FWingActorComponent> 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);
};