Work on blueprint components in MCP
This commit is contained in:
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
{
|
||||
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"));
|
||||
}
|
||||
};
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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));
|
||||
}
|
||||
};
|
||||
@@ -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"));
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
for (FWingProperty& P : Props)
|
||||
if (Props.IsEmpty())
|
||||
{
|
||||
FString Category = P->HasMetaData(TEXT("Category")) ? P->GetMetaData(TEXT("Category")) : FString();
|
||||
ByCategory.FindOrAdd(Category).Add(P);
|
||||
UWingServer::Print(TEXT(" (no blueprint-visible properties found)\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 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)
|
||||
FString CurrentCategory;
|
||||
for (FWingProperty& P : Props)
|
||||
{
|
||||
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("...");
|
||||
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),
|
||||
*PropName,
|
||||
*ValueStr);
|
||||
*WingUtils::FormatName(P.Prop),
|
||||
*P.GetTruncatedText(100));
|
||||
}
|
||||
}
|
||||
|
||||
if (Props.IsEmpty())
|
||||
UWingServer::Print(TEXT(" (no blueprint-visible properties found)\n"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,26 +291,18 @@ 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);
|
||||
SetObj(Comp->SCSNode);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
UWingServer::Printf(TEXT("ERROR: Component '%s' not found in %s\n"), *Value, *BP->GetName());
|
||||
return SetError();
|
||||
}
|
||||
|
||||
WingFetcher& WingFetcher::LevelBlueprint(const FString& Value)
|
||||
{
|
||||
|
||||
@@ -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)
|
||||
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)
|
||||
{
|
||||
for (TFieldIterator<FProperty> It(Container->GetClass()); It; ++It)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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 -----
|
||||
|
||||
Reference in New Issue
Block a user