Massive overhaul of the WingActorComponent system, can now edit component properties better, lots of bug fixes

This commit is contained in:
2026-03-27 05:16:49 -04:00
parent 3bbddde491
commit a5ab6b02f9
19 changed files with 380 additions and 222 deletions

View File

@@ -48,16 +48,8 @@ public:
UBlueprint* BP = F.Asset(Blueprint).Cast<UBlueprint>();
if (!BP) return;
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS)
{
UWingServer::Printf(TEXT("ERROR: Blueprint '%s' does not have a SimpleConstructionScript (not an Actor Blueprint)\n"),
*WingUtils::FormatName(BP));
return;
}
// Check that the proposed name is valid
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
TArray<UWingComponentReference*> AllComponents = UWingComponentReference::GetAll(BP);
if (!WingUtils::FindExactlyNoneNamed(Component, AllComponents, TEXT("Component"))) return;
FString InternalName = WingUtils::CheckProposedName(Component);
if (InternalName.IsEmpty()) return;
@@ -70,25 +62,15 @@ public:
Req.IsChildOf = UActorComponent::StaticClass();
UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class, Req);
if (!ComponentClass) return;
if (!UWingComponentReference::CheckValidComponentClass(ComponentClass)) return;
// Find the specified parent component
FWingActorComponent* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component"));
UWingComponentReference* ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component"));
if (!ParentComp) return;
// Create the SCS node
USCS_Node *NewNode = FWingActorComponent::AddComponent(BP, ComponentClass, ParentComp, InternalName);
if (!NewNode)
{
UWingServer::Printf(TEXT("ERROR: Failed to create SCS node for component '%s' with class '%s'\n"),
*Component, *WingUtils::FormatName(ComponentClass));
return;
}
if (!UWingComponentReference::AddComponent(BP, ComponentClass, ParentComp, FName(*InternalName))) return;
UWingServer::Printf(TEXT("Added component %s (%s)"),
*WingUtils::FormatName(NewNode),
*WingUtils::FormatName(ComponentClass));
if (ParentComp)
UWingServer::Printf(TEXT(" under %s"), *ParentComp->GetName());
UWingServer::Print(TEXT("\n"));
UWingServer::Printf(TEXT("Component Added.\n"));
}
};

View File

@@ -53,7 +53,7 @@ public:
UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::SanitizeName(Node->ParentComponentOrVariableName));
// Properties (already sorted and grouped by category)
TArray<FWingProperty> Props = FWingProperty::GetAll(Template, CPF_Edit);
TArray<FWingProperty> Props = FWingProperty::GetDetailsMutable(Template, CPF_Edit);
if (Props.IsEmpty())
{

View File

@@ -36,22 +36,17 @@ public:
virtual void Handle() override
{
WingFetcher F;
USCS_Node* Node = F.Walk(Component).Cast<USCS_Node>();
if (!Node) return;
USimpleConstructionScript* SCS = Node->GetSCS();
UWingComponentReference* CompRef = F.Walk(Component).Cast<UWingComponentReference>();
if (!CompRef) return;
// Find the new parent among all components (if specified)
UBlueprint *BP = SCS->GetBlueprint();
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
FWingActorComponent* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component"));
UBlueprint *BP = CompRef->BP;
TArray<UWingComponentReference*> AllComponents = UWingComponentReference::GetAll(BP);
UWingComponentReference* NewParent = WingUtils::FindExactlyOneNamed(Parent, AllComponents, TEXT("Component"));
if (!NewParent) return;
if (!FWingActorComponent::ReparentComponent(BP,
Node, NewParent))
return;
if (!CompRef->ReparentComponent(NewParent)) return;
UWingServer::Printf(TEXT("Reparented %s under %s.\n"),
*WingUtils::FormatName(Node), *WingUtils::FormatName(*NewParent));
UWingServer::Printf(TEXT("Reparented component."));
}
};

View File

@@ -83,17 +83,18 @@ public:
}
// Components
TArray<FWingActorComponent> Components = FWingActorComponent::GetAll(BP);
if (!Components.IsEmpty())
TArray<UWingComponentReference*> Components3 = UWingComponentReference::GetAll(BP);
if (!Components3.IsEmpty())
{
UWingServer::Print(TEXT("\nComponents:\n"));
for (const FWingActorComponent& Comp : Components)
for (const UWingComponentReference* Ref : Components3)
{
UWingServer::Printf(TEXT(" %s %s"), *Comp.GetClassName(), *WingUtils::FormatName(Comp));
FString ParentName = Comp.GetParentName();
if (!ParentName.IsEmpty())
UWingServer::Printf(TEXT(" [parent: %s]"), *ParentName);
if (!Comp.IsOwnedBy(BP))
UWingServer::Printf(TEXT(" %s %s"),
*Ref->TypeName,
*WingUtils::FormatName(Ref));
if (!Ref->ParentName.IsEmpty())
UWingServer::Printf(TEXT(" [parent: %s]"), *Ref->ParentName);
if (Ref->Inherited)
UWingServer::Print(TEXT(" [inherited]"));
UWingServer::Print(TEXT("\n"));
}

View File

@@ -91,7 +91,7 @@ public:
UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>();
if (!Node) return;
TArray<FWingProperty> All = FWingProperty::GetAll(Node, CPF_Edit);
TArray<FWingProperty> All = FWingProperty::GetDetailsMutable(Node, CPF_Edit);
FWingProperty P = FWingProperty::FindOneExactMatch(All, Entry.Name);
if (!P) return;

View File

@@ -40,7 +40,7 @@ public:
WingFetcher F;
UObject* Template = F.Walk(Object).Cast<UObject>();
if (!Template) return;
TArray<FWingProperty> AllProps = FWingProperty::GetAll(Template, CPF_Edit);
TArray<FWingProperty> AllProps = FWingProperty::GetDetailsImmutable(Template, CPF_Edit);
TArray<FWingProperty> Props = FWingProperty::FindAllSubstring(AllProps, Query);
if (Local)
{

View File

@@ -36,7 +36,7 @@ public:
UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!Obj) return;
TArray<FWingProperty> All = FWingProperty::GetAll(Obj, CPF_Edit);
TArray<FWingProperty> All = FWingProperty::GetDetailsMutable(Obj, CPF_Edit);
FWingProperty P = FWingProperty::FindOneExactMatch(All, Property);
if (!P) return;

View File

@@ -42,7 +42,7 @@ public:
UWingServer::Print(TEXT("Error: No properties specified\n"));
return;
}
TArray<FWingProperty> All = FWingProperty::GetAll(Obj, CPF_Edit);
TArray<FWingProperty> All = FWingProperty::GetDetailsMutable(Obj, CPF_Edit);
int SuccessCount = 0;
// Validation pass — resolve all properties and values before modifying anything.

View File

@@ -1,5 +1,6 @@
#include "WingActorComponent.h"
#include "WingServer.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/SCS_Node.h"
@@ -8,191 +9,250 @@
#include "Components/SceneComponent.h"
#include "GameFramework/Actor.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Engine/InheritableComponentHandler.h"
FWingActorComponent::FWingActorComponent(USCS_Node *Node) :
SCSNode(Node), Owner(Node->GetSCS()->GetBlueprint()->GeneratedClass) {}
FString FWingActorComponent::GetName() const
UActorComponent *UWingComponentReference::FindComponentInCDO(UClass *Class, FName Name)
{
if (SCSNode) return WingUtils::FormatName(SCSNode);
if (NativeComponent) return WingUtils::FormatName(NativeComponent);
return FString();
if (!Class) return nullptr;
AActor* CDO = Cast<AActor>(Class->GetDefaultObject());
if (!CDO) return nullptr;
return FindObjectFast<UActorComponent>(CDO, Name);
}
FString FWingActorComponent::GetClassName() const
USCS_Node* UWingComponentReference::FindSCSNodeByName(UBlueprint *BP, FName Name)
{
if (SCSNode)
return SCSNode->ComponentClass ? WingUtils::FormatName(SCSNode->ComponentClass) : TEXT("None");
if (NativeComponent)
return WingUtils::FormatName(NativeComponent->GetClass());
return FString();
}
FString FWingActorComponent::GetParentName() const
{
if (SCSNode)
for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(BP))
{
// Cross-blueprint case: parent is inherited, stored by name.
if (SCSNode->ParentComponentOrVariableName != NAME_None)
return WingUtils::SanitizeName(SCSNode->ParentComponentOrVariableName);
// Same-blueprint case: find the node whose child list contains us.
for (USCS_Node* Candidate : SCSNode->GetSCS()->GetAllNodes())
{
if (Candidate->ChildNodes.Contains(SCSNode))
return WingUtils::FormatName(Candidate);
}
return FString();
if (!WalkBP->SimpleConstructionScript) continue;
USCS_Node* Node = WalkBP->SimpleConstructionScript->FindSCSNode(Name);
if (Node != nullptr) return Node;
}
if (NativeComponent)
{
if (USceneComponent* Scene = Cast<USceneComponent>(NativeComponent))
if (USceneComponent* Parent = Scene->GetAttachParent())
return WingUtils::FormatName(Parent);
return FString();
}
return FString();
return nullptr;
}
bool FWingActorComponent::IsOwnedBy(const UBlueprint* BP) const
UWingComponentReference::FoundComponent UWingComponentReference::FindComponent(UBlueprint *BP, FName Name)
{
return BP && BP->GeneratedClass == Owner;
UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(BP->ParentClass);
FoundComponent Result;
Result.Name = Name;
Result.SCS = FindSCSNodeByName(BP, Name);
Result.Native = FindComponentInCDO(NativeClass, Name);
return Result;
}
bool FWingActorComponent::CheckEditableComponent(UBlueprint *BP, const FWingActorComponent *Component)
bool UWingComponentReference::CheckValidParent(UWingComponentReference::FoundComponent FC)
{
if ((!Component) || (Component->IsEmpty()))
if (FC.SCS && FC.Native)
{
UWingServer::Printf(TEXT("Component does not exist.\n"));
UWingServer::Printf(TEXT("Weirdness, two components named %s\n"),
*WingUtils::SanitizeName(FC.Name));
return false;
}
if (Component->IsNative())
if ((FC.SCS == nullptr) && (FC.Native == nullptr))
{
UWingServer::Printf(TEXT("Cannot edit native components.\n"));
UWingServer::Printf(TEXT("No such parent component: %s\n"),
*WingUtils::SanitizeName(FC.Name));
return false;
}
if (!Component->IsOwnedBy(BP))
if (FC.Native && (!Cast<USceneComponent>(FC.Native)))
{
UWingServer::Printf(TEXT("Component %s is inherited, to edit it, you must edit BP %s\n"),
*WingUtils::FormatName(*Component), *WingUtils::FormatName(Component->Owner));
UWingServer::Printf(TEXT("Not a SceneComponent, so cannot be parent: %s\n"),
*WingUtils::SanitizeName(FC.Name));
return false;
}
return true;
}
bool FWingActorComponent::CheckValidParent(const FWingActorComponent *Parent)
bool UWingComponentReference::CheckNoSuchComponent(FoundComponent FC)
{
if ((!Parent) || (Parent->IsEmpty()))
if (FC.SCS || FC.Native)
{
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));
UWingServer::Printf(TEXT("A component named %s already exists"),
*WingUtils::SanitizeName(FC.Name));
return false;
}
return true;
}
bool FWingActorComponent::CheckValidComponentClass(UClass *Class)
bool UWingComponentReference::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));
UWingServer::Printf(TEXT("Class does not derive from ActorComponent: %s\n"),
*WingUtils::FormatName(Class));
return false;
}
return true;
}
void FWingActorComponent::AddNodeInternal(UBlueprint *BP, USCS_Node *NewNode, const FWingActorComponent *Parent)
void UWingComponentReference::AddChildNode(UBlueprint *BP, USCS_Node *NewNode, FoundComponent Parent)
{
if (Parent->SCSNode && Parent->IsOwnedBy(BP))
if (Parent.SCS)
{
Parent->SCSNode->AddChildNode(NewNode, true);
}
else if (Parent->SCSNode)
{
NewNode->SetParent(Parent->SCSNode);
BP->SimpleConstructionScript->AddNode(NewNode);
if (Parent.SCS->GetSCS() == BP->SimpleConstructionScript)
{
Parent.SCS->AddChildNode(NewNode);
}
else
{
NewNode->SetParent(Parent.SCS);
BP->SimpleConstructionScript->AddNode(NewNode);
}
}
else
{
NewNode->SetParent(Cast<USceneComponent>(Parent->NativeComponent));
NewNode->SetParent(Cast<USceneComponent>(Parent.Native));
BP->SimpleConstructionScript->AddNode(NewNode);
}
}
USCS_Node *FWingActorComponent::AddComponent(UBlueprint *BP, UClass *Class, const FWingActorComponent* Parent, const FString &Name)
bool UWingComponentReference::AddComponent(UBlueprint *BP, UClass *Class, UWingComponentReference *Parent, FName Name)
{
if (!CheckValidParent(Parent)) return nullptr;
if (!CheckValidComponentClass(Class)) return nullptr;
FoundComponent ExistingComponent = FindComponent(BP, Name);
if (!CheckNoSuchComponent(ExistingComponent)) return false;
FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName);
if (!CheckValidParent(ParentComponent)) return false;
if (!CheckValidComponentClass(Class)) return false;
USCS_Node *NewNode = BP->SimpleConstructionScript->CreateNode(Class, FName(*Name));
USCS_Node *NewNode = BP->SimpleConstructionScript->CreateNode(Class, 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"));
*WingUtils::SanitizeName(Name), *WingUtils::FormatName(Class));
return false;
}
BP->SimpleConstructionScript->RemoveNode(SCSNode);
AddNodeInternal(BP, SCSNode, Parent);
AddChildNode(BP, NewNode, ParentComponent);
return true;
}
TArray<FWingActorComponent> FWingActorComponent::GetAll(UBlueprint* BP)
bool UWingComponentReference::ReparentComponent(UWingComponentReference *Parent)
{
TArray<FWingActorComponent> Result;
if (!BP) return Result;
FoundComponent ParentComponent = FindComponent(BP, Parent->VariableName);
if (!CheckValidParent(ParentComponent)) return false;
FoundComponent ThisComponent = FindComponent(BP, VariableName);
UClass* GenClass = BP->GeneratedClass;
if (!GenClass) return Result;
// Native components from CDO
if (AActor* CDO = Cast<AActor>(GenClass->GetDefaultObject()))
if (ThisComponent.SCS == nullptr)
{
UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(GenClass);
TArray<UActorComponent*> NativeComponents;
CDO->GetComponents(NativeComponents);
for (UActorComponent* Comp : NativeComponents)
Result.Emplace(Comp, NativeClass);
UWingServer::Printf(TEXT("Cannot reparent native components."));
return false;
}
if (ThisComponent.SCS->GetSCS() != BP->SimpleConstructionScript)
{
UWingServer::Printf(TEXT("Component belongs to blueprint %s, edit that blueprint to reparent it."),
*WingUtils::FormatName(ThisComponent.SCS->GetSCS()->GetBlueprint()));
return false;
}
if (ParentComponent.SCS && ParentComponent.SCS->IsChildOf(ThisComponent.SCS))
{
UWingServer::Printf(TEXT("Cannot parent a component to itself or its own child."));
return false;
}
// SCS nodes, walking hierarchy from oldest ancestor to current BP
TArray<UBlueprint*> Hierarchy;
UBlueprint::GetBlueprintHierarchyFromClass(GenClass, Hierarchy);
for (int32 i = Hierarchy.Num() - 1; i >= 0; --i)
BP->SimpleConstructionScript->RemoveNode(ThisComponent.SCS);
AddChildNode(BP, ThisComponent.SCS, ParentComponent);
return true;
}
TMap<FName, FName> UWingComponentReference::CalculateParentNames(USimpleConstructionScript *Script)
{
TMap<FName, FName> ParentNames;
for (USCS_Node* Node : Script->GetAllNodes())
{
UBlueprint* WalkBP = Hierarchy[i];
if (!WalkBP->SimpleConstructionScript) continue;
ParentNames.Add(Node->GetVariableName(), Node->ParentComponentOrVariableName);
}
for (USCS_Node* Parent : Script->GetAllNodes())
{
for (USCS_Node *Child : Parent->GetChildNodes())
{
ParentNames[Child->GetVariableName()] = Parent->GetVariableName();
}
}
return ParentNames;
}
UActorComponent* UWingComponentReference::GetImmutableTemplate() const
{
if (Native) return FindComponentInCDO(BP->GeneratedClass, VariableName);
USCS_Node *Node = FindSCSNodeByName(BP, VariableName);
if (Node == nullptr) return nullptr;
FComponentKey Key(Node);
for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(BP, false))
{
if (Node->GetSCS()->GetBlueprint() == WalkBP) return Node->ComponentTemplate;
UInheritableComponentHandler* ICH = WalkBP->GetInheritableComponentHandler(/*bCreateIfNecessary=*/ false);
if (!ICH) continue;
UActorComponent* Override = ICH->GetOverridenComponentTemplate(Key);
if (Override) return Override;
}
return nullptr;
}
UActorComponent* UWingComponentReference::GetMutableTemplate() const
{
if (Native) return FindComponentInCDO(BP->GeneratedClass, VariableName);
USCS_Node* Node = FindSCSNodeByName(BP, VariableName);
if (!Node) return nullptr;
if (Node->GetSCS()->GetBlueprint() == BP) return Node->ComponentTemplate;
FComponentKey Key(Node);
UInheritableComponentHandler* ICH = BP->GetInheritableComponentHandler(/*bCreateIfNecessary=*/ true);
if (!ICH) return nullptr;
UActorComponent* Override = ICH->GetOverridenComponentTemplate(Key);
if (!Override) Override = ICH->CreateOverridenComponentTemplate(Key);
return Override;
}
TArray<UWingComponentReference*> UWingComponentReference::GetAll(UBlueprint* BP)
{
TArray<UWingComponentReference*> Result;
if (!BP) return Result;
// Find the native ancestor class
UClass* NativeClass = FBlueprintEditorUtils::FindFirstNativeClass(BP->ParentClass);
// Native components from the native ancestor's CDO
if (NativeClass)
{
if (AActor* CDO = Cast<AActor>(NativeClass->GetDefaultObject()))
{
TArray<UActorComponent*> NativeComponents;
CDO->GetComponents(NativeComponents);
for (UActorComponent* Comp : NativeComponents)
{
UWingComponentReference* Ref = NewObject<UWingComponentReference>();
Ref->BP = BP;
Ref->VariableName = Comp->GetFName();
Ref->TypeName = UWingTypes::TypeToText(Comp->GetClass());
if (USceneComponent* Scene = Cast<USceneComponent>(Comp))
if (USceneComponent* AttachParent = Scene->GetAttachParent())
Ref->ParentName = WingUtils::SanitizeName(AttachParent->GetFName());
Ref->Native = true;
Ref->Inherited = true;
Result.Add(Ref);
}
}
}
// Visit all SCS nodes, oldest first.
for (UBlueprint *WalkBP : WingUtils::GetAncestorBlueprints(BP, true))
{
if (WalkBP->SimpleConstructionScript == nullptr) continue;
TMap<FName, FName> ParentNames = CalculateParentNames(WalkBP->SimpleConstructionScript);
for (USCS_Node* Node : WalkBP->SimpleConstructionScript->GetAllNodes())
Result.Emplace(Node);
{
UWingComponentReference* Ref = NewObject<UWingComponentReference>();
Ref->BP = BP;
Ref->VariableName = Node->GetVariableName();
Ref->TypeName = UWingTypes::TypeToText(Node->ComponentClass);
Ref->ParentName = WingUtils::SanitizeName(ParentNames[Node->GetVariableName()]);
Ref->Native = false;
Ref->Inherited = (WalkBP != BP);
Result.Add(Ref);
}
}
return Result;
}

View File

@@ -302,25 +302,19 @@ WingFetcher& WingFetcher::Component(const FString& Value)
return SetError();
}
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
FWingActorComponent* Comp = WingUtils::FindExactlyOneNamed(Value, AllComponents, TEXT("component"));
if (!Comp)
TArray<UWingComponentReference*> AllComponents = UWingComponentReference::GetAll(BP);
UWingComponentReference* Found = WingUtils::FindExactlyOneNamed(Value, AllComponents, TEXT("component"));
if (!Found)
{
UWingServer::Printf(TEXT("Components that exist in the blueprint:\n"));
for (const FWingActorComponent &C : AllComponents)
for (const UWingComponentReference* C : AllComponents)
{
UWingServer::Printf(TEXT(" %s\n"), *WingUtils::FormatName(C));
}
return SetError();
}
if (!Comp->IsOwnedBy(BP))
{
UWingServer::Printf(TEXT("ERROR: Component '%s' belongs to %s, to edit it, you must go through that blueprint.\n"),
*Comp->GetName(), *WingUtils::FormatName(Comp->Owner));
return SetError();
}
SetObj(Comp->SCSNode);
SetObj(Found);
return *this;
}

View File

@@ -275,7 +275,7 @@ void WingGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderB
UMaterialExpression* Expression = MatNode->MaterialExpression;
FString PrimaryCategory = Expression->GetClass()->GetName();
TArray<FWingProperty> Props = FWingProperty::GetAll(Expression, CPF_Edit);
TArray<FWingProperty> Props = FWingProperty::GetDetailsMutable(Expression, CPF_Edit);
for (const FWingProperty& WP : Props)
{

View File

@@ -1,4 +1,5 @@
#include "WingProperty.h"
#include "WingActorComponent.h"
#include "WingUtils.h"
#include "WingHandler.h"
#include "WingServer.h"
@@ -256,14 +257,12 @@ void FWingProperty::Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In,
In.SetNum(Dst);
}
TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
TArray<FWingProperty> FWingProperty::GetDetailsGeneral(UObject* Obj, EPropertyFlags Flags, bool Mutable)
{
if (!Obj) return {};
// Blueprints don't have editable properties. So
// instead, we fetch properties from the generated CDO,
// which is probably what the user intended.
//
// instead, we fetch properties from the generated CDO.
if (UBlueprint *BP = ::Cast<UBlueprint>(Obj))
{
if (BP->GeneratedClass == nullptr)
@@ -274,17 +273,15 @@ TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
Obj = BP->GeneratedClass->GetDefaultObject();
}
// SCS nodes don't have useful editable properties.
// Redirect to the component template instead.
//
if (USCS_Node* Node = ::Cast<USCS_Node>(Obj))
// Component references: get the proper template.
if (UWingComponentReference* Ref = ::Cast<UWingComponentReference>(Obj))
{
if (!Node->ComponentTemplate)
Obj = Mutable ? Ref->GetMutableTemplate() : Ref->GetImmutableTemplate();
if (!Obj)
{
UWingServer::Printf(TEXT("ERROR: SCS node '%s' has no component template\n"), *Obj->GetName());
UWingServer::Printf(TEXT("ERROR: Component '%s' has no template\n"), *Ref->VariableName.ToString());
return {};
}
Obj = Node->ComponentTemplate;
}
TArray<FWingProperty> Result;
@@ -315,6 +312,11 @@ TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
return Result;
}
TArray<FWingProperty> FWingProperty::GetAll(UObject* Object, EPropertyFlags Flags)
{
return GetAll(Object->GetClass(), Object, Flags);
}
TArray<FWingProperty> FWingProperty::GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags)
{
TArray<FWingProperty> Result;

View File

@@ -269,9 +269,9 @@ FString WingUtils::FormatName(const FBPInterfaceDescription &IFace)
return FormatName(IFace.Interface);
}
FString WingUtils::FormatName(const FWingActorComponent &Comp)
FString WingUtils::FormatName(const UWingComponentReference *Ref)
{
return SanitizeName(Comp.GetName());
return SanitizeName(Ref->VariableName);
}
FString WingUtils::FormatName(const UWidget *Widget)
@@ -473,6 +473,18 @@ TArray<UEdGraphNode*> WingUtils::AllNodes(UEdGraph *Graph)
return Result;
}
TArray<UBlueprint*> WingUtils::GetAncestorBlueprints(UBlueprint *BP, bool OldestFirst)
{
TArray<UBlueprint*> Blueprints;
for (UBlueprint* WalkBP = BP; WalkBP; )
{
Blueprints.Add(WalkBP);
WalkBP = UBlueprint::GetBlueprintFromClass(WalkBP->ParentClass);
}
if (OldestFirst) Algo::Reverse(Blueprints);
return Blueprints;
}
// ============================================================
// Material helpers
// ============================================================

View File

@@ -6,42 +6,62 @@
class UBlueprint;
class USCS_Node;
class UActorComponent;
class USimpleConstructionScript;
USTRUCT()
struct FWingActorComponent
// a 'Component Reference' is basically just a pointer to a
// blueprint plus a component name. The other fields in here
// are purely for convenience of printing things out, they
// are *never* used for any analysis.
//
UCLASS()
class UWingComponentReference : public UObject
{
GENERATED_BODY()
USCS_Node* SCSNode = nullptr;
UActorComponent* NativeComponent = nullptr;
UClass* Owner = nullptr;
public:
// The blueprint that was queried: not necessarily the
// same blueprint that defined the component.
UPROPERTY()
UBlueprint* BP = nullptr;
// The raw component name, not sanitized yet.
FName VariableName;
FWingActorComponent() = default;
FWingActorComponent(USCS_Node* Node);
FWingActorComponent(UActorComponent* InNative, UClass* InOwner) : NativeComponent(InNative), Owner(InOwner) {}
// Sanitized Parent Name (for display only)
FString ParentName;
explicit operator bool() const { return SCSNode || NativeComponent; }
// Sanitized TypeName of the component (for display only)
FString TypeName;
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;
// True if the component is native (for display only)
bool Native = false;
// Add a new SCS node, underneath the existing parent.
static USCS_Node *AddComponent(UBlueprint *BP, UClass *Class, const FWingActorComponent* Parent, const FString &Name);
// True if the component was inherited (for display only)
bool Inherited = false;
// Reparent an existing SCS node to a specified parent.
static bool ReparentComponent(UBlueprint *BP, USCS_Node *Component, const FWingActorComponent *Parent);
UActorComponent* GetImmutableTemplate() const;
UActorComponent* GetMutableTemplate() const;
bool ReparentComponent(UWingComponentReference *Parent);
bool DeleteComponent();
static bool AddComponent(UBlueprint *BP, UClass *Class, UWingComponentReference *Parent, FName Name);
// 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);
static TArray<UWingComponentReference*> 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);
private:
struct FoundComponent
{
FName Name;
USCS_Node *SCS;
UActorComponent *Native;
};
static UActorComponent *FindComponentInCDO(UClass *Class, FName Name);
static USCS_Node* FindSCSNodeByName(UBlueprint *BP, FName VariableName);
static FoundComponent FindComponent(UBlueprint *BP, FName Name);
static bool CheckValidParent(FoundComponent FC);
static bool CheckNoSuchComponent(FoundComponent FC);
static void AddChildNode(UBlueprint *BP, USCS_Node *Node, FoundComponent Parent);
static TMap<FName, FName> CalculateParentNames(USimpleConstructionScript *Script);
};

View File

@@ -28,19 +28,53 @@ struct FWingProperty
explicit operator bool() const { return Prop != nullptr; }
FProperty* operator->() const { return Prop; }
static TArray<FWingProperty> GetAll(UObject* Obj, EPropertyFlags Flags);
// Return the "Details Panel" properties for the specified object.
//
// This is the set of properties that would typically appear in the
// details panel if you were to click the object in the editor. Note
// that this is not always the properties of the object itself. For
// example, if you click a widget, the details panel shows you
// properties of the widget, but also properties of the slot. If you
// click a material graph node, the details panel shows you properties
// of the node, but also properties of the material expression.
//
// When editing an inherited ActorComponent, you're actually editing
// properties that *override* the original properties of the ActorComponent.
// The mutable version, 'GetDetailsMutable', tells this function to first
// create the overrides, and then edit those.
//
// For a more direct "just give me the properties of this object,"
// use the GetAll function.
//
static TArray<FWingProperty> GetDetailsMutable(UObject* Obj, EPropertyFlags Flags)
{ return GetDetailsGeneral(Obj, Flags, true); }
static TArray<FWingProperty> GetDetailsImmutable(UObject *Obj, EPropertyFlags Flags)
{ return GetDetailsGeneral(Obj, Flags, false); }
// Get the raw properties of the specified object or struct.
//
// This gets the properties that are literally present in the
// specified object or struct. No special interpretation is done.
//
static TArray<FWingProperty> GetAll(UObject* Object, EPropertyFlags Flags);
static TArray<FWingProperty> GetAll(UStruct* StructType, void* Container, EPropertyFlags Flags);
// Functions to find items by name in an array of properties.
//
static TArray<FWingProperty> FindAllSubstring(const TArray<FWingProperty>& Props, const FString& Substring);
static FWingProperty FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name);
static void Remove(TArray<FWingProperty>& Props, const FString& Name);
static void Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In, const FString &Name);
// Functions to populate properties from a JSON object.
//
static bool PopulateFromJson(FWingProperty& Prop, const FJsonObject* Json, bool AllOptional = false);
static bool PopulateFromJson(TArray<FWingProperty>& Props, const FJsonObject* Json, bool AllOptional = false);
static bool PopulateFromJson(UStruct* StructType, void* Container, const FJsonObject* Object);
static bool PopulateFromJson(UStruct* StructType, void* Container, const TSharedPtr<FJsonValue>& Object);
private:
static TArray<FWingProperty> GetDetailsGeneral(UObject* Obj, EPropertyFlags Flags, bool Mutable);
void PrintExpectsReceived(const TCHAR *Type);
static void Collect(UStruct* Struct, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags);
};

View File

@@ -34,6 +34,7 @@ struct FBPVariableDescription;
struct FUserPinInfo;
struct FBPInterfaceDescription;
struct FWingActorComponent;
class UWingComponentReference;
// Stateless utility functions used by MCP handlers and the MCP server.
// This is effectively a namespace — all methods are static.
@@ -80,6 +81,7 @@ public:
static FString FormatName(const FUserPinInfo &Pin);
static FString FormatName(const FBPInterfaceDescription &IFace);
static FString FormatName(const FWingActorComponent &Comp);
static FString FormatName(const UWingComponentReference *Ref);
static FString FormatName(const UWidget *Widget);
////////////////////////////////////////////////////////
@@ -208,7 +210,7 @@ public:
static TArray<UEdGraph*> AllGraphs(UBlueprint* BP);
static TArray<UEdGraphNode*> AllNodes(UBlueprint* BP);
static TArray<UEdGraphNode*> AllNodes(UEdGraph *Graph);
static TArray<UBlueprint*> GetAncestorBlueprints(UBlueprint *BP, bool OldestFirst = false);
// ----- Material helpers -----
static void EnsureMaterialGraph(UMaterial* Material);