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"))
|
UPROPERTY(meta=(Description="Component name for the new component"))
|
||||||
FString 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;
|
FString Parent;
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
@@ -62,39 +62,26 @@ public:
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Resolve the component class by name
|
// Resolve the component class by name
|
||||||
UClass* ComponentClassObj = UWingTypes::TextToOneObjectType(Class);
|
UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class);
|
||||||
if (!ComponentClassObj) return;
|
if (!ComponentClass) return;
|
||||||
if (!ComponentClassObj->IsChildOf(UActorComponent::StaticClass()))
|
|
||||||
{
|
|
||||||
UWingServer::Printf(TEXT("ERROR: '%s' is not a subclass of UActorComponent\n"), *Class);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If parent component specified, find it among all components
|
// Find the specified parent component
|
||||||
FWingActorComponent* ParentComp = nullptr;
|
FWingActorComponent* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents);
|
||||||
if (!Parent.IsEmpty())
|
if (!ParentComp) return;
|
||||||
{
|
|
||||||
ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents);
|
|
||||||
if (!ParentComp) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the SCS node
|
// 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)
|
if (!NewNode)
|
||||||
{
|
{
|
||||||
UWingServer::Printf(TEXT("ERROR: Failed to create SCS node for component '%s' with class '%s'\n"),
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to the hierarchy
|
|
||||||
if (!FWingActorComponent::SetParent(NewNode, ParentComp))
|
|
||||||
return;
|
|
||||||
SCS->AddNode(NewNode);
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Added component %s (%s)"),
|
UWingServer::Printf(TEXT("Added component %s (%s)"),
|
||||||
*WingUtils::FormatName(NewNode->ComponentTemplate),
|
*WingUtils::FormatName(NewNode),
|
||||||
*WingUtils::FormatName(ComponentClassObj));
|
*WingUtils::FormatName(ComponentClass));
|
||||||
if (ParentComp)
|
if (ParentComp)
|
||||||
UWingServer::Printf(TEXT(" under %s"), *ParentComp->GetName());
|
UWingServer::Printf(TEXT(" under %s"), *ParentComp->GetName());
|
||||||
UWingServer::Print(TEXT("\n"));
|
UWingServer::Print(TEXT("\n"));
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public:
|
|||||||
UPROPERTY(meta=(Description="Path to the component (e.g. '/Game/MyBP,component:MyComp')"))
|
UPROPERTY(meta=(Description="Path to the component (e.g. '/Game/MyBP,component:MyComp')"))
|
||||||
FString Component;
|
FString Component;
|
||||||
|
|
||||||
UPROPERTY(meta=(Optional, Description="New parent component name. If empty, detaches to root."))
|
UPROPERTY(meta=(Description="New parent component name."))
|
||||||
FString Parent;
|
FString Parent;
|
||||||
|
|
||||||
virtual FString GetDescription() const override
|
virtual FString GetDescription() const override
|
||||||
@@ -40,25 +40,18 @@ public:
|
|||||||
if (!Node) return;
|
if (!Node) return;
|
||||||
|
|
||||||
USimpleConstructionScript* SCS = Node->GetSCS();
|
USimpleConstructionScript* SCS = Node->GetSCS();
|
||||||
if (!SCS) return;
|
|
||||||
|
|
||||||
// Find the new parent among all components (if specified)
|
// Find the new parent among all components (if specified)
|
||||||
FWingActorComponent* NewParent = nullptr;
|
UBlueprint *BP = SCS->GetBlueprint();
|
||||||
TArray<FWingActorComponent> AllComponents;
|
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
|
||||||
if (!Parent.IsEmpty())
|
FWingActorComponent* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents);
|
||||||
{
|
if (!NewParent) return;
|
||||||
UBlueprint* BP = SCS->GetBlueprint();
|
|
||||||
AllComponents = FWingActorComponent::GetAll(BP);
|
|
||||||
NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents);
|
|
||||||
if (!NewParent) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!FWingActorComponent::SetParent(Node, NewParent))
|
if (!FWingActorComponent::ReparentComponent(BP,
|
||||||
|
Node, NewParent))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (NewParent)
|
UWingServer::Printf(TEXT("Reparented %s under %s.\n"),
|
||||||
UWingServer::Printf(TEXT("Reparented %s under %s.\n"), *WingUtils::FormatName(Node), *NewParent->GetName());
|
*WingUtils::FormatName(Node), *WingUtils::FormatName(*NewParent));
|
||||||
else
|
|
||||||
UWingServer::Printf(TEXT("Detached %s to root.\n"), *WingUtils::FormatName(Node));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ public:
|
|||||||
UWingServer::Print(TEXT("\nComponents:\n"));
|
UWingServer::Print(TEXT("\nComponents:\n"));
|
||||||
for (const FWingActorComponent& Comp : Components)
|
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();
|
FString ParentName = Comp.GetParentName();
|
||||||
if (!ParentName.IsEmpty())
|
if (!ParentName.IsEmpty())
|
||||||
UWingServer::Printf(TEXT(" [parent: %s]"), *ParentName);
|
UWingServer::Printf(TEXT(" [parent: %s]"), *ParentName);
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
#include "GameFramework/Actor.h"
|
#include "GameFramework/Actor.h"
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
|
|
||||||
|
FWingActorComponent::FWingActorComponent(USCS_Node *Node) :
|
||||||
|
SCSNode(Node), Owner(Node->GetSCS()->GetBlueprint()->GeneratedClass) {}
|
||||||
|
|
||||||
FString FWingActorComponent::GetName() const
|
FString FWingActorComponent::GetName() const
|
||||||
{
|
{
|
||||||
if (SCSNode) return WingUtils::FormatName(SCSNode);
|
if (SCSNode) return WingUtils::FormatName(SCSNode);
|
||||||
@@ -48,56 +51,112 @@ bool FWingActorComponent::IsOwnedBy(const UBlueprint* BP) const
|
|||||||
return BP && BP->GeneratedClass == Owner;
|
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 ((!Component) || (Component->IsEmpty()))
|
||||||
if (Parent)
|
|
||||||
{
|
{
|
||||||
if (Parent->SCSNode)
|
UWingServer::Printf(TEXT("Component does not exist.\n"));
|
||||||
{
|
return false;
|
||||||
// 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<USceneComponent>(Parent->NativeComponent);
|
|
||||||
if (!NativeScene)
|
|
||||||
{
|
|
||||||
UWingServer::Printf(TEXT("ERROR: Native parent '%s' is not a SceneComponent — cannot attach to it.\n"),
|
|
||||||
*Parent->GetName());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (Component->IsNative())
|
||||||
// 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)
|
|
||||||
{
|
{
|
||||||
ChildNode->SetParent(Parent->SCSNode);
|
UWingServer::Printf(TEXT("Cannot edit native components.\n"));
|
||||||
return true;
|
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<USceneComponent>(Parent->NativeComponent));
|
|
||||||
return true;
|
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<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("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> FWingActorComponent::GetAll(UBlueprint* BP)
|
TArray<FWingActorComponent> FWingActorComponent::GetAll(UBlueprint* BP)
|
||||||
{
|
{
|
||||||
TArray<FWingActorComponent> Result;
|
TArray<FWingActorComponent> Result;
|
||||||
@@ -123,9 +182,8 @@ TArray<FWingActorComponent> FWingActorComponent::GetAll(UBlueprint* BP)
|
|||||||
{
|
{
|
||||||
UBlueprint* WalkBP = Hierarchy[i];
|
UBlueprint* WalkBP = Hierarchy[i];
|
||||||
if (!WalkBP->SimpleConstructionScript) continue;
|
if (!WalkBP->SimpleConstructionScript) continue;
|
||||||
UClass* OwnerClass = WalkBP->GeneratedClass;
|
|
||||||
for (USCS_Node* Node : WalkBP->SimpleConstructionScript->GetAllNodes())
|
for (USCS_Node* Node : WalkBP->SimpleConstructionScript->GetAllNodes())
|
||||||
Result.Emplace(Node, OwnerClass);
|
Result.Emplace(Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result;
|
return Result;
|
||||||
|
|||||||
@@ -115,20 +115,15 @@ WingFetcher& WingFetcher::Asset(const FString& PackagePath)
|
|||||||
{
|
{
|
||||||
if (bError) return *this;
|
if (bError) return *this;
|
||||||
|
|
||||||
// Try to find the object in memory first (silent), then load if needed.
|
// Check if the package exists before calling LoadObject, because
|
||||||
// LoadObject logs its own errors when the package doesn't exist, so
|
// LoadObject logs its own errors when the package doesn't exist.
|
||||||
// we check DoesPackageExist first to avoid redundant log spam.
|
FString PackageName = FPackageName::ObjectPathToPackageName(PackagePath);
|
||||||
SetObj(FindObject<UObject>(nullptr, *PackagePath));
|
if (!FPackageName::DoesPackageExist(PackageName))
|
||||||
if (!Obj)
|
|
||||||
{
|
{
|
||||||
FString PackageName = FPackageName::ObjectPathToPackageName(PackagePath);
|
UWingServer::Printf(TEXT("ERROR: Asset '%s' does not exist.\n"), *PackagePath);
|
||||||
if (!FPackageName::DoesPackageExist(PackageName))
|
return SetError();
|
||||||
{
|
|
||||||
UWingServer::Printf(TEXT("ERROR: Asset '%s' does not exist.\n"), *PackagePath);
|
|
||||||
return SetError();
|
|
||||||
}
|
|
||||||
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
|
|
||||||
}
|
}
|
||||||
|
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
|
||||||
if (!Obj)
|
if (!Obj)
|
||||||
{
|
{
|
||||||
UWingServer::Printf(TEXT("ERROR: Could not load asset '%s'\n"), *PackagePath);
|
UWingServer::Printf(TEXT("ERROR: Could not load asset '%s'\n"), *PackagePath);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "Materials/MaterialExpression.h"
|
#include "Materials/MaterialExpression.h"
|
||||||
#include "Kismet2/BlueprintEditorUtils.h"
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||||||
#include "MaterialEditingLibrary.h"
|
#include "MaterialEditingLibrary.h"
|
||||||
|
#include "Subsystems/AssetEditorSubsystem.h"
|
||||||
|
|
||||||
void FWingNotifier::AddTouchedObject(UObject* Obj)
|
void FWingNotifier::AddTouchedObject(UObject* Obj)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ struct FWingActorComponent
|
|||||||
UClass* Owner = nullptr;
|
UClass* Owner = nullptr;
|
||||||
|
|
||||||
FWingActorComponent() = default;
|
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) {}
|
FWingActorComponent(UActorComponent* InNative, UClass* InOwner) : NativeComponent(InNative), Owner(InOwner) {}
|
||||||
|
|
||||||
explicit operator bool() const { return SCSNode || NativeComponent; }
|
explicit operator bool() const { return SCSNode || NativeComponent; }
|
||||||
@@ -24,15 +24,24 @@ struct FWingActorComponent
|
|||||||
FString GetName() const;
|
FString GetName() const;
|
||||||
FString GetClassName() const;
|
FString GetClassName() const;
|
||||||
FString GetParentName() const;
|
FString GetParentName() const;
|
||||||
|
bool IsEmpty() const { return SCSNode == nullptr && NativeComponent == nullptr; }
|
||||||
bool IsNative() const { return NativeComponent != nullptr; }
|
bool IsNative() const { return NativeComponent != nullptr; }
|
||||||
bool IsOwnedBy(const UBlueprint* BP) const;
|
bool IsOwnedBy(const UBlueprint* BP) const;
|
||||||
|
|
||||||
// Attach ChildNode under this component. If this is an SCS node, uses
|
// Add a new SCS node, underneath the existing parent.
|
||||||
// AddChildNode. If native, uses SetParent with the native scene component.
|
static USCS_Node *AddComponent(UBlueprint *BP, UClass *Class, const FWingActorComponent* Parent, const FString &Name);
|
||||||
// Returns false and prints an error if attachment is not possible.
|
|
||||||
static bool SetParent(USCS_Node* ChildNode, const FWingActorComponent* Parent);
|
// 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
|
// Collect all components: native from CDO first, then SCS nodes
|
||||||
// walking the hierarchy from oldest ancestor to current blueprint.
|
// walking the hierarchy from oldest ancestor to current blueprint.
|
||||||
static TArray<FWingActorComponent> GetAll(UBlueprint* BP);
|
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