Work on blueprint components in MCP

This commit is contained in:
2026-03-19 15:53:25 -04:00
parent 2e4606c9e4
commit 56f2257dd9
15 changed files with 385 additions and 202 deletions

View File

@@ -1,91 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#include "Blueprint_RemoveComponent.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_RemoveComponent : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Component to remove"))
FString Component;
virtual FString GetDescription() const override
{
return TEXT("Remove a component from a Blueprint's SimpleConstructionScript.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS)
{
UWingServer::Print(TEXT("ERROR: Not an Actor Blueprint (no SimpleConstructionScript).\n"));
return;
}
// Find the node to remove using Identifies for consistent name matching
USCS_Node* NodeToRemove = nullptr;
const TArray<USCS_Node*>& AllNodes = SCS->GetAllNodes();
for (USCS_Node* Node : AllNodes)
{
if (Node && Node->ComponentTemplate &&
WingUtils::Identifies(Component, Node->ComponentTemplate))
{
NodeToRemove = Node;
break;
}
}
if (!NodeToRemove)
{
UWingServer::Printf(TEXT("ERROR: Component '%s' not found.\nAvailable components:\n"),
*Component);
for (USCS_Node* Node : AllNodes)
{
if (Node && Node->ComponentTemplate)
UWingServer::Printf(TEXT(" %s\n"), *WingUtils::FormatName(Node->ComponentTemplate));
}
return;
}
// Prevent removing the root scene component if it has children
const TArray<USCS_Node*>& RootNodes = SCS->GetRootNodes();
if (RootNodes.Contains(NodeToRemove) && NodeToRemove->GetChildNodes().Num() > 0)
{
UWingServer::Printf(TEXT("ERROR: Cannot remove '%s' — it is a root component with %d child(ren). "
"Remove or re-parent the children first.\n"),
*WingUtils::FormatName(NodeToRemove->ComponentTemplate),
NodeToRemove->GetChildNodes().Num());
return;
}
FString RemovedName = WingUtils::FormatName(NodeToRemove->ComponentTemplate);
// Remove the node (promotes children to parent if it has any — but we've guarded root above)
SCS->RemoveNodeAndPromoteChildren(NodeToRemove);
UWingServer::Printf(TEXT("Removed component %s.\n"), *RemovedName);
}
};

View File

@@ -6,11 +6,12 @@
#include "WingUtils.h"
#include "WingTypes.h"
#include "WingServer.h"
#include "WingActorComponent.h"
#include "Engine/Blueprint.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#include "Components/ActorComponent.h"
#include "Blueprint_AddComponent.generated.h"
#include "BlueprintComponent_Add.generated.h"
// ---------------------------------------------------------------------------
@@ -18,7 +19,7 @@
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_AddComponent : public UObject, public IWingHandler
class UWing_BlueprintComponent_Add : public UObject, public IWingHandler
{
GENERATED_BODY()
@@ -55,9 +56,9 @@ public:
return;
}
// Check for duplicate component names
const TArray<USCS_Node*>& Existing = SCS->GetAllNodes();
if (!WingUtils::FindExactlyNoneNamed(Component, Existing))
// Check for duplicate component names (including native/inherited)
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
if (!WingUtils::FindExactlyNoneNamed(Component, AllComponents))
return;
// Resolve the component class by name
@@ -69,12 +70,12 @@ public:
return;
}
// If parent component specified, find its SCS node
USCS_Node* ParentSCSNode = nullptr;
// If parent component specified, find it among all components
FWingActorComponent* ParentComp = nullptr;
if (!Parent.IsEmpty())
{
ParentSCSNode = WingUtils::FindExactlyOneNamed(Parent, Existing);
if (!ParentSCSNode) return;
ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents);
if (!ParentComp) return;
}
// Create the SCS node
@@ -87,22 +88,15 @@ public:
}
// Add to the hierarchy
if (ParentSCSNode)
{
ParentSCSNode->AddChildNode(NewNode);
}
else
{
SCS->AddNode(NewNode);
}
if (!FWingActorComponent::SetParent(NewNode, ParentComp))
return;
SCS->AddNode(NewNode);
UWingServer::Printf(TEXT("Added component %s (%s)"),
*WingUtils::FormatName(NewNode->ComponentTemplate),
*WingUtils::FormatName(ComponentClassObj));
if (ParentSCSNode)
{
UWingServer::Printf(TEXT(" under %s"), *WingUtils::FormatName(ParentSCSNode->ComponentTemplate));
}
if (ParentComp)
UWingServer::Printf(TEXT(" under %s"), *ParentComp->GetName());
UWingServer::Print(TEXT("\n"));
}
};

View File

@@ -0,0 +1,82 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingProperty.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "WingActorComponent.h"
#include "Engine/SCS_Node.h"
#include "Engine/SimpleConstructionScript.h"
#include "BlueprintComponent_Dump.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintComponent_Dump : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Path to the component (e.g. '/Game/MyBP,component:MyComp')"))
FString Component;
virtual FString GetDescription() const override
{
return TEXT("Dump a component's class, parent, and editable properties.");
}
virtual void Handle() override
{
WingFetcher F;
USCS_Node* Node = F.Walk(Component).Cast<USCS_Node>();
if (!Node) return;
UActorComponent* Template = Node->ComponentTemplate;
if (!Template)
{
UWingServer::Print(TEXT("ERROR: SCS node has no component template.\n"));
return;
}
// Header
UWingServer::Printf(TEXT("Component: %s\n"), *WingUtils::FormatName(Node));
UWingServer::Printf(TEXT("Class: %s\n"), *WingUtils::FormatName(Node->ComponentClass));
// Parent
if (Node->ParentComponentOrVariableName != NAME_None)
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);
if (Props.IsEmpty())
{
UWingServer::Print(TEXT("\n (no editable properties found)\n"));
return;
}
FString CurrentCategory;
for (FWingProperty& P : Props)
{
FString Category = P.GetCategory();
if (Category != CurrentCategory)
{
CurrentCategory = Category;
UWingServer::Printf(TEXT("\n%s:\n"), *CurrentCategory);
}
bool bEditable = !P->HasAnyPropertyFlags(CPF_EditConst);
UWingServer::Printf(TEXT(" %s %s %s = %s\n"),
bEditable ? TEXT("editable") : TEXT("readonly"),
*UWingTypes::TypeToText(P.Prop),
*WingUtils::FormatName(P.Prop),
*P.GetTruncatedText(100));
}
}
};

View File

@@ -0,0 +1,55 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Engine/SCS_Node.h"
#include "Engine/SimpleConstructionScript.h"
#include "BlueprintComponent_Remove.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintComponent_Remove : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Path to the component (e.g. '/Game/MyBP,component:MyComp')"))
FString Component;
virtual FString GetDescription() const override
{
return TEXT("Remove a component from a Blueprint's SimpleConstructionScript.");
}
virtual void Handle() override
{
WingFetcher F;
USCS_Node* Node = F.Walk(Component).Cast<USCS_Node>();
if (!Node) return;
USimpleConstructionScript* SCS = Node->GetSCS();
if (!SCS) return;
// Prevent removing a root component that has children
const TArray<USCS_Node*>& RootNodes = SCS->GetRootNodes();
if (RootNodes.Contains(Node) && Node->GetChildNodes().Num() > 0)
{
UWingServer::Printf(TEXT("ERROR: Cannot remove '%s' — it is a root component with %d children. "
"Remove or re-parent the children first.\n"),
*WingUtils::FormatName(Node),
Node->GetChildNodes().Num());
return;
}
FString RemovedName = WingUtils::FormatName(Node);
SCS->RemoveNodeAndPromoteChildren(Node);
UWingServer::Printf(TEXT("Removed component %s.\n"), *RemovedName);
}
};

View File

@@ -0,0 +1,64 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingServer.h"
#include "WingActorComponent.h"
#include "Engine/Blueprint.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#include "BlueprintComponent_Reparent.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintComponent_Reparent : public UObject, public IWingHandler
{
GENERATED_BODY()
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."))
FString Parent;
virtual FString GetDescription() const override
{
return TEXT("Change the parent of a component in a Blueprint's SimpleConstructionScript.");
}
virtual void Handle() override
{
WingFetcher F;
USCS_Node* Node = F.Walk(Component).Cast<USCS_Node>();
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);
if (!NewParent) return;
}
if (!FWingActorComponent::SetParent(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));
}
};

View File

@@ -89,9 +89,7 @@ public:
FString ParentName = Comp.GetParentName();
if (!ParentName.IsEmpty())
UWingServer::Printf(TEXT(" [parent: %s]"), *ParentName);
if (Comp.IsNative())
UWingServer::Print(TEXT(" [native]"));
else if (!Comp.IsOwnedBy(BP))
if (!Comp.IsOwnedBy(BP))
UWingServer::Print(TEXT(" [inherited]"));
UWingServer::Print(TEXT("\n"));
}

View File

@@ -26,9 +26,6 @@ public:
UPROPERTY(meta=(Optional, Description="Substring filter for property names"))
FString Query;
UPROPERTY(meta=(Optional, Description="Truncate values to 80 characters (default true)"))
bool Truncate = true;
UPROPERTY(meta=(Optional, Description="Only show properties declared on the object's own class, not inherited ones"))
bool Local = false;
@@ -51,48 +48,28 @@ public:
Props.RemoveAll([ObjClass](const FWingProperty& P) { return P->GetOwnerStruct() != ObjClass; });
}
// Group properties by category.
TMap<FString, TArray<FWingProperty>> ByCategory;
if (Props.IsEmpty())
{
UWingServer::Print(TEXT(" (no blueprint-visible properties found)\n"));
return;
}
FString CurrentCategory;
for (FWingProperty& P : Props)
{
FString Category = P->HasMetaData(TEXT("Category")) ? P->GetMetaData(TEXT("Category")) : FString();
ByCategory.FindOrAdd(Category).Add(P);
}
// Sort category names, putting empty category last.
TArray<FString> Categories;
ByCategory.GetKeys(Categories);
Categories.Sort([](const FString& A, const FString& B) {
if (A.IsEmpty()) return false;
if (B.IsEmpty()) return true;
return A < B;
});
for (const FString& Category : Categories)
{
if (Category.IsEmpty())
UWingServer::Print(TEXT("\nUncategorized:\n"));
else
UWingServer::Printf(TEXT("\n%s:\n"), *Category);
for (FWingProperty& P : ByCategory[Category])
FString Category = P.GetCategory();
if (Category != CurrentCategory)
{
FString PropName = WingUtils::FormatName(P.Prop);
FString ValueStr = P.GetText();
if (Truncate && (ValueStr.Len() > 80))
ValueStr = ValueStr.Left(80) + TEXT("...");
bool bEditable = !P->HasAnyPropertyFlags(CPF_EditConst);
UWingServer::Printf(TEXT(" %s %s %s = %s\n"),
bEditable ? TEXT("editable") : TEXT("readonly"),
*UWingTypes::TypeToText(P.Prop),
*PropName,
*ValueStr);
CurrentCategory = Category;
UWingServer::Printf(TEXT("\n%s:\n"), *CurrentCategory);
}
}
if (Props.IsEmpty())
UWingServer::Print(TEXT(" (no blueprint-visible properties found)\n"));
bool bEditable = !P->HasAnyPropertyFlags(CPF_EditConst);
UWingServer::Printf(TEXT(" %s %s %s = %s\n"),
bEditable ? TEXT("editable") : TEXT("readonly"),
*UWingTypes::TypeToText(P.Prop),
*WingUtils::FormatName(P.Prop),
*P.GetTruncatedText(100));
}
}
};

View File

@@ -1,4 +1,5 @@
#include "WingActorComponent.h"
#include "WingServer.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "Engine/SCS_Node.h"
@@ -47,6 +48,56 @@ bool FWingActorComponent::IsOwnedBy(const UBlueprint* BP) const
return BP && BP->GeneratedClass == Owner;
}
bool FWingActorComponent::SetParent(USCS_Node* ChildNode, const FWingActorComponent* Parent)
{
// Validate before modifying anything
if (Parent)
{
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"));
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;
}
}
}
// 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);
return true;
}
ChildNode->SetParent(Cast<USceneComponent>(Parent->NativeComponent));
return true;
}
TArray<FWingActorComponent> FWingActorComponent::GetAll(UBlueprint* BP)
{
TArray<FWingActorComponent> Result;

View File

@@ -1,6 +1,7 @@
#include "WingFetcher.h"
#include "WingServer.h"
#include "WingUtils.h"
#include "WingActorComponent.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
@@ -290,25 +291,17 @@ WingFetcher& WingFetcher::Component(const FString& Value)
if (!BP)
return TypeMismatch(TEXT("component"), TEXT("Blueprint"));
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
if (!SCS)
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
FWingActorComponent* Comp = WingUtils::FindExactlyOneNamed(Value, AllComponents);
if (!Comp) return SetError();
if (!Comp->IsOwnedBy(BP))
{
UWingServer::Printf(TEXT("ERROR: Blueprint %s has no SimpleConstructionScript (not an Actor Blueprint)\n"), *BP->GetName());
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();
}
FName SearchName(*Value);
for (USCS_Node* SCSNode : SCS->GetAllNodes())
{
if (SCSNode && SCSNode->GetVariableName() == SearchName)
{
SetObj(SCSNode->ComponentTemplate);
return *this;
}
}
UWingServer::Printf(TEXT("ERROR: Component '%s' not found in %s\n"), *Value, *BP->GetName());
return SetError();
SetObj(Comp->SCSNode);
return *this;
}
WingFetcher& WingFetcher::LevelBlueprint(const FString& Value)

View File

@@ -19,6 +19,13 @@ FWingProperty::FWingProperty(FProperty* InProp, void* InContainer)
FWingProperty::FWingProperty(FProperty* InProp, UObject* InContainer)
: Prop(InProp), Container(static_cast<void*>(InContainer)) {}
FString FWingProperty::GetCategory()
{
FString Result = Prop->GetMetaData(TEXT("Category"));
if (Result.IsEmpty()) Result = "Unclassified";
return Result;
}
FString FWingProperty::GetText() const
{
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
@@ -29,6 +36,16 @@ FString FWingProperty::GetText() const
return Result;
}
FString FWingProperty::GetTruncatedText(int32 MaxLen) const
{
FString Result = GetText();
for (int i = 0; i < Result.Len(); i++)
if (Result[i] == '\n') Result[i] = ' ';
if (Result.Len() > MaxLen)
Result = Result.Left(MaxLen) + TEXT("...");
return Result;
}
bool FWingProperty::SetText(const FString &Value)
{
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
@@ -75,19 +92,26 @@ bool FWingProperty::SetText(const FString &Value)
void FWingProperty::Collect(UStruct* StructType, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags)
{
TMap<FString, TArray<FWingProperty>> Grouped;
for (TFieldIterator<FProperty> It(StructType); It; ++It)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Props.Emplace(*It, Container);
FString SortCat = *It->GetMetaData(TEXT("Category"));
Grouped.FindOrAdd(SortCat).Add(FWingProperty(*It, Container));
}
}
TArray<FString> Categories;
void FWingProperty::Collect(UObject* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags)
{
for (TFieldIterator<FProperty> It(Container->GetClass()); It; ++It)
Grouped.GetKeys(Categories);
Categories.Sort([](const FString& A, const FString& B) {
if (A.IsEmpty()) return false;
if (B.IsEmpty()) return true;
return A < B;
});
for (const FString& Category : Categories)
{
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
Props.Emplace(*It, Container);
Props.Append(Grouped[Category]);
}
}
@@ -99,7 +123,6 @@ void FWingProperty::Remove(TArray<FWingProperty>& Props, const FString& Name)
TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
{
if (!Obj) return {};
TArray<FWingProperty> Result;
// Blueprints don't have editable properties. So
// instead, we fetch properties from the generated CDO,
@@ -115,7 +138,8 @@ TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
Obj = BP->GeneratedClass->GetDefaultObject();
}
Collect(Obj, Result, Flags);
TArray<FWingProperty> Result;
Collect(Obj->GetClass(), Obj, Result, Flags);
// If it's a Material Graph node, also collect properties from
// the associated material expression.
@@ -124,9 +148,10 @@ TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
{
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
{
Collect(Expr, Result, Flags);
Collect(Expr->GetClass(), Expr, Result, Flags);
}
}
return Result;
}

View File

@@ -24,8 +24,7 @@ FString UWingTypes::GetProposedName(const UObject *Obj)
Name.LeftChopInline(2);
}
}
WingUtils::SanitizeNameInPlace(Name);
return Name;
return WingUtils::SanitizeName(Name);
}
void UWingTypes::ReserveShortName(FName Name)

View File

@@ -46,16 +46,11 @@
// ============================================================
// Name sanitization
//
// Our parsers reserve certain punctuation marks for parsing
// types, paths, and the like. For example: Array<Int>.
// We therefore cannot allow those specific punctuation marks
// in names. We replace them with similar-looking unicode
// characters.
// ============================================================
void WingUtils::SanitizeNameInPlace(FString &Name)
FString WingUtils::SanitizeName(const FString &InName)
{
FString Name = InName;
int32 Dst = 0;
for (int32 Src = 0; Src < Name.Len(); Src++)
{
@@ -64,25 +59,32 @@ void WingUtils::SanitizeNameInPlace(FString &Name)
if (c == ' ') c=L'·';
if (c == '<') c=L'';
if (c == '>') c=L'';
if (c == ',') c=L'·';
if (c == ',') c=L'';
Name[Dst++] = c;
}
if (Dst == 0) Name[Dst++] = L'·';
Name.LeftInline(Dst);
return Name;
}
FString WingUtils::SanitizeName(const FString &Name)
FString WingUtils::UnsanitizeName(const FString &InName)
{
FString Result = Name;
SanitizeNameInPlace(Result);
return Result;
FString Name = InName;
for (int32 i = 0; i < Name.Len(); i++)
{
TCHAR c = Name[i];
if (c == L'·') c=' ';
if (c == L'') c='<';
if (c == L'') c='>';
if (c == L'') c=',';
Name[i] = c;
}
return Name;
}
FString WingUtils::SanitizeName(FName Name)
{
FString Result = Name.ToString();
SanitizeNameInPlace(Result);
return Result;
return SanitizeName(Name.ToString());
}
// ============================================================

View File

@@ -1,13 +1,16 @@
#pragma once
#include "CoreMinimal.h"
#include "WingActorComponent.generated.h"
class UBlueprint;
class USCS_Node;
class UActorComponent;
USTRUCT()
struct FWingActorComponent
{
GENERATED_BODY()
USCS_Node* SCSNode = nullptr;
UActorComponent* NativeComponent = nullptr;
UClass* Owner = nullptr;
@@ -24,6 +27,11 @@ struct FWingActorComponent
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);
// 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);

View File

@@ -17,6 +17,13 @@ struct FWingProperty
FString GetText() const;
bool SetText(const FString& Value);
// Get the Category metadata.
FString GetCategory();
// Get the text, replace newlines with whitespace, and
// truncate to the specified maximum length.
FString GetTruncatedText(int32 MaxLen) const;
explicit operator bool() const { return Prop != nullptr; }
FProperty* operator->() const { return Prop; }
@@ -27,6 +34,5 @@ struct FWingProperty
static FWingProperty FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name);
private:
static void Collect(UStruct* StructType, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags);
static void Collect(UObject* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags);
static void Collect(UStruct* Struct, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags);
};

View File

@@ -155,9 +155,29 @@ public:
////////////////////////////////////////////////////////
static void SanitizeNameInPlace(FString& Name);
////////////////////////////////////////////////////////
// Our parsers reserve certain punctuation marks for parsing
// types, paths, and the like. For example: Array<Int>.
// We therefore cannot allow those specific punctuation marks
// in names. We replace them with similar-looking unicode
// characters.
////////////////////////////////////////////////////////
static FString SanitizeName(const FString& Name);
static FString SanitizeName(FName Name);
////////////////////////////////////////////////////////
// Our name sanitization routine, above, will turn
// names with spaces into names like "Post·Initiate·Action"
// containing middle dots instead. There is a risk that the
// LLM will see these dotted names and think it is supposed to
// do that. So, we have an 'Unsanitize' routine to convert
// the middle dots back into spaces. Of course, next time the
// name is output, it will be sanitized again, so the LLM will
// always see the version with dots. This creates consistency
// for both the LLM and the human user (who is expecting whitespace).
////////////////////////////////////////////////////////
static FString UnsanitizeName(const FString& Name);
static FString FormatNodeTitle(const UEdGraphNode *Node);
// ----- Enum helpers -----