More work on blueprint component lists
This commit is contained in:
Binary file not shown.
BIN
Content/Testing/BP1.uasset
LFS
Normal file
BIN
Content/Testing/BP1.uasset
LFS
Normal file
Binary file not shown.
@@ -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"));
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "Materials/MaterialExpression.h"
|
||||
#include "Kismet2/BlueprintEditorUtils.h"
|
||||
#include "MaterialEditingLibrary.h"
|
||||
#include "Subsystems/AssetEditorSubsystem.h"
|
||||
|
||||
void FWingNotifier::AddTouchedObject(UObject* Obj)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user