Massive overhaul of the WingActorComponent system, can now edit component properties better, lots of bug fixes
This commit is contained in:
@@ -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"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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."));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
// ============================================================
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user