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 "WingUtils.h"
|
||||||
#include "WingTypes.h"
|
#include "WingTypes.h"
|
||||||
#include "WingServer.h"
|
#include "WingServer.h"
|
||||||
|
#include "WingActorComponent.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "Engine/SimpleConstructionScript.h"
|
#include "Engine/SimpleConstructionScript.h"
|
||||||
#include "Engine/SCS_Node.h"
|
#include "Engine/SCS_Node.h"
|
||||||
#include "Components/ActorComponent.h"
|
#include "Components/ActorComponent.h"
|
||||||
#include "Blueprint_AddComponent.generated.h"
|
#include "BlueprintComponent_Add.generated.h"
|
||||||
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -18,7 +19,7 @@
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
UCLASS()
|
UCLASS()
|
||||||
class UWing_Blueprint_AddComponent : public UObject, public IWingHandler
|
class UWing_BlueprintComponent_Add : public UObject, public IWingHandler
|
||||||
{
|
{
|
||||||
GENERATED_BODY()
|
GENERATED_BODY()
|
||||||
|
|
||||||
@@ -55,9 +56,9 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicate component names
|
// Check for duplicate component names (including native/inherited)
|
||||||
const TArray<USCS_Node*>& Existing = SCS->GetAllNodes();
|
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
|
||||||
if (!WingUtils::FindExactlyNoneNamed(Component, Existing))
|
if (!WingUtils::FindExactlyNoneNamed(Component, AllComponents))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Resolve the component class by name
|
// Resolve the component class by name
|
||||||
@@ -69,12 +70,12 @@ public:
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If parent component specified, find its SCS node
|
// If parent component specified, find it among all components
|
||||||
USCS_Node* ParentSCSNode = nullptr;
|
FWingActorComponent* ParentComp = nullptr;
|
||||||
if (!Parent.IsEmpty())
|
if (!Parent.IsEmpty())
|
||||||
{
|
{
|
||||||
ParentSCSNode = WingUtils::FindExactlyOneNamed(Parent, Existing);
|
ParentComp = WingUtils::FindExactlyOneNamed(Parent, AllComponents);
|
||||||
if (!ParentSCSNode) return;
|
if (!ParentComp) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the SCS node
|
// Create the SCS node
|
||||||
@@ -87,22 +88,15 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add to the hierarchy
|
// Add to the hierarchy
|
||||||
if (ParentSCSNode)
|
if (!FWingActorComponent::SetParent(NewNode, ParentComp))
|
||||||
{
|
return;
|
||||||
ParentSCSNode->AddChildNode(NewNode);
|
SCS->AddNode(NewNode);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
SCS->AddNode(NewNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
UWingServer::Printf(TEXT("Added component %s (%s)"),
|
UWingServer::Printf(TEXT("Added component %s (%s)"),
|
||||||
*WingUtils::FormatName(NewNode->ComponentTemplate),
|
*WingUtils::FormatName(NewNode->ComponentTemplate),
|
||||||
*WingUtils::FormatName(ComponentClassObj));
|
*WingUtils::FormatName(ComponentClassObj));
|
||||||
if (ParentSCSNode)
|
if (ParentComp)
|
||||||
{
|
UWingServer::Printf(TEXT(" under %s"), *ParentComp->GetName());
|
||||||
UWingServer::Printf(TEXT(" under %s"), *WingUtils::FormatName(ParentSCSNode->ComponentTemplate));
|
|
||||||
}
|
|
||||||
UWingServer::Print(TEXT("\n"));
|
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();
|
FString ParentName = Comp.GetParentName();
|
||||||
if (!ParentName.IsEmpty())
|
if (!ParentName.IsEmpty())
|
||||||
UWingServer::Printf(TEXT(" [parent: %s]"), *ParentName);
|
UWingServer::Printf(TEXT(" [parent: %s]"), *ParentName);
|
||||||
if (Comp.IsNative())
|
if (!Comp.IsOwnedBy(BP))
|
||||||
UWingServer::Print(TEXT(" [native]"));
|
|
||||||
else if (!Comp.IsOwnedBy(BP))
|
|
||||||
UWingServer::Print(TEXT(" [inherited]"));
|
UWingServer::Print(TEXT(" [inherited]"));
|
||||||
UWingServer::Print(TEXT("\n"));
|
UWingServer::Print(TEXT("\n"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ public:
|
|||||||
UPROPERTY(meta=(Optional, Description="Substring filter for property names"))
|
UPROPERTY(meta=(Optional, Description="Substring filter for property names"))
|
||||||
FString Query;
|
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"))
|
UPROPERTY(meta=(Optional, Description="Only show properties declared on the object's own class, not inherited ones"))
|
||||||
bool Local = false;
|
bool Local = false;
|
||||||
|
|
||||||
@@ -51,48 +48,28 @@ public:
|
|||||||
Props.RemoveAll([ObjClass](const FWingProperty& P) { return P->GetOwnerStruct() != ObjClass; });
|
Props.RemoveAll([ObjClass](const FWingProperty& P) { return P->GetOwnerStruct() != ObjClass; });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Group properties by category.
|
if (Props.IsEmpty())
|
||||||
TMap<FString, TArray<FWingProperty>> ByCategory;
|
{
|
||||||
|
UWingServer::Print(TEXT(" (no blueprint-visible properties found)\n"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FString CurrentCategory;
|
||||||
for (FWingProperty& P : Props)
|
for (FWingProperty& P : Props)
|
||||||
{
|
{
|
||||||
FString Category = P->HasMetaData(TEXT("Category")) ? P->GetMetaData(TEXT("Category")) : FString();
|
FString Category = P.GetCategory();
|
||||||
ByCategory.FindOrAdd(Category).Add(P);
|
if (Category != CurrentCategory)
|
||||||
}
|
|
||||||
|
|
||||||
// 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 PropName = WingUtils::FormatName(P.Prop);
|
CurrentCategory = Category;
|
||||||
FString ValueStr = P.GetText();
|
UWingServer::Printf(TEXT("\n%s:\n"), *CurrentCategory);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (Props.IsEmpty())
|
bool bEditable = !P->HasAnyPropertyFlags(CPF_EditConst);
|
||||||
UWingServer::Print(TEXT(" (no blueprint-visible properties found)\n"));
|
UWingServer::Printf(TEXT(" %s %s %s = %s\n"),
|
||||||
|
bEditable ? TEXT("editable") : TEXT("readonly"),
|
||||||
|
*UWingTypes::TypeToText(P.Prop),
|
||||||
|
*WingUtils::FormatName(P.Prop),
|
||||||
|
*P.GetTruncatedText(100));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "WingActorComponent.h"
|
#include "WingActorComponent.h"
|
||||||
|
#include "WingServer.h"
|
||||||
#include "WingUtils.h"
|
#include "WingUtils.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "Engine/SCS_Node.h"
|
#include "Engine/SCS_Node.h"
|
||||||
@@ -47,6 +48,56 @@ bool FWingActorComponent::IsOwnedBy(const UBlueprint* BP) const
|
|||||||
return BP && BP->GeneratedClass == Owner;
|
return BP && BP->GeneratedClass == Owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FWingActorComponent::SetParent(USCS_Node* ChildNode, const FWingActorComponent* Parent)
|
||||||
|
{
|
||||||
|
// 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> FWingActorComponent::GetAll(UBlueprint* BP)
|
||||||
{
|
{
|
||||||
TArray<FWingActorComponent> Result;
|
TArray<FWingActorComponent> Result;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "WingFetcher.h"
|
#include "WingFetcher.h"
|
||||||
#include "WingServer.h"
|
#include "WingServer.h"
|
||||||
#include "WingUtils.h"
|
#include "WingUtils.h"
|
||||||
|
#include "WingActorComponent.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
#include "EdGraph/EdGraphNode.h"
|
#include "EdGraph/EdGraphNode.h"
|
||||||
@@ -290,25 +291,17 @@ WingFetcher& WingFetcher::Component(const FString& Value)
|
|||||||
if (!BP)
|
if (!BP)
|
||||||
return TypeMismatch(TEXT("component"), TEXT("Blueprint"));
|
return TypeMismatch(TEXT("component"), TEXT("Blueprint"));
|
||||||
|
|
||||||
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
|
TArray<FWingActorComponent> AllComponents = FWingActorComponent::GetAll(BP);
|
||||||
if (!SCS)
|
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();
|
return SetError();
|
||||||
}
|
}
|
||||||
|
SetObj(Comp->SCSNode);
|
||||||
FName SearchName(*Value);
|
return *this;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WingFetcher& WingFetcher::LevelBlueprint(const FString& Value)
|
WingFetcher& WingFetcher::LevelBlueprint(const FString& Value)
|
||||||
|
|||||||
@@ -19,6 +19,13 @@ FWingProperty::FWingProperty(FProperty* InProp, void* InContainer)
|
|||||||
FWingProperty::FWingProperty(FProperty* InProp, UObject* InContainer)
|
FWingProperty::FWingProperty(FProperty* InProp, UObject* InContainer)
|
||||||
: Prop(InProp), Container(static_cast<void*>(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
|
FString FWingProperty::GetText() const
|
||||||
{
|
{
|
||||||
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
||||||
@@ -29,6 +36,16 @@ FString FWingProperty::GetText() const
|
|||||||
return Result;
|
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)
|
bool FWingProperty::SetText(const FString &Value)
|
||||||
{
|
{
|
||||||
void* ValuePtr = Prop->ContainerPtrToValuePtr<void>(Container);
|
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)
|
void FWingProperty::Collect(UStruct* StructType, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags)
|
||||||
{
|
{
|
||||||
|
TMap<FString, TArray<FWingProperty>> Grouped;
|
||||||
|
|
||||||
for (TFieldIterator<FProperty> It(StructType); It; ++It)
|
for (TFieldIterator<FProperty> It(StructType); It; ++It)
|
||||||
{
|
{
|
||||||
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
|
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) {
|
||||||
for (TFieldIterator<FProperty> It(Container->GetClass()); It; ++It)
|
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.Append(Grouped[Category]);
|
||||||
Props.Emplace(*It, Container);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +123,6 @@ void FWingProperty::Remove(TArray<FWingProperty>& Props, const FString& Name)
|
|||||||
TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
|
TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
|
||||||
{
|
{
|
||||||
if (!Obj) return {};
|
if (!Obj) return {};
|
||||||
TArray<FWingProperty> Result;
|
|
||||||
|
|
||||||
// Blueprints don't have editable properties. So
|
// Blueprints don't have editable properties. So
|
||||||
// instead, we fetch properties from the generated CDO,
|
// instead, we fetch properties from the generated CDO,
|
||||||
@@ -115,7 +138,8 @@ TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
|
|||||||
Obj = BP->GeneratedClass->GetDefaultObject();
|
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
|
// If it's a Material Graph node, also collect properties from
|
||||||
// the associated material expression.
|
// the associated material expression.
|
||||||
@@ -124,9 +148,10 @@ TArray<FWingProperty> FWingProperty::GetAll(UObject* Obj, EPropertyFlags Flags)
|
|||||||
{
|
{
|
||||||
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
|
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
|
||||||
{
|
{
|
||||||
Collect(Expr, Result, Flags);
|
Collect(Expr->GetClass(), Expr, Result, Flags);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Result;
|
return Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,8 +24,7 @@ FString UWingTypes::GetProposedName(const UObject *Obj)
|
|||||||
Name.LeftChopInline(2);
|
Name.LeftChopInline(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WingUtils::SanitizeNameInPlace(Name);
|
return WingUtils::SanitizeName(Name);
|
||||||
return Name;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UWingTypes::ReserveShortName(FName Name)
|
void UWingTypes::ReserveShortName(FName Name)
|
||||||
|
|||||||
@@ -46,16 +46,11 @@
|
|||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Name sanitization
|
// 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;
|
int32 Dst = 0;
|
||||||
for (int32 Src = 0; Src < Name.Len(); Src++)
|
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'▷';
|
if (c == '>') c=L'▷';
|
||||||
if (c == ',') c=L'·';
|
if (c == ',') c=L'▾';
|
||||||
Name[Dst++] = c;
|
Name[Dst++] = c;
|
||||||
}
|
}
|
||||||
if (Dst == 0) Name[Dst++] = L'·';
|
if (Dst == 0) Name[Dst++] = L'·';
|
||||||
Name.LeftInline(Dst);
|
Name.LeftInline(Dst);
|
||||||
|
return Name;
|
||||||
}
|
}
|
||||||
|
|
||||||
FString WingUtils::SanitizeName(const FString &Name)
|
FString WingUtils::UnsanitizeName(const FString &InName)
|
||||||
{
|
{
|
||||||
FString Result = Name;
|
FString Name = InName;
|
||||||
SanitizeNameInPlace(Result);
|
for (int32 i = 0; i < Name.Len(); i++)
|
||||||
return Result;
|
{
|
||||||
|
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 WingUtils::SanitizeName(FName Name)
|
||||||
{
|
{
|
||||||
FString Result = Name.ToString();
|
return SanitizeName(Name.ToString());
|
||||||
SanitizeNameInPlace(Result);
|
|
||||||
return Result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "WingActorComponent.generated.h"
|
||||||
|
|
||||||
class UBlueprint;
|
class UBlueprint;
|
||||||
class USCS_Node;
|
class USCS_Node;
|
||||||
class UActorComponent;
|
class UActorComponent;
|
||||||
|
|
||||||
|
USTRUCT()
|
||||||
struct FWingActorComponent
|
struct FWingActorComponent
|
||||||
{
|
{
|
||||||
|
GENERATED_BODY()
|
||||||
USCS_Node* SCSNode = nullptr;
|
USCS_Node* SCSNode = nullptr;
|
||||||
UActorComponent* NativeComponent = nullptr;
|
UActorComponent* NativeComponent = nullptr;
|
||||||
UClass* Owner = nullptr;
|
UClass* Owner = nullptr;
|
||||||
@@ -24,6 +27,11 @@ struct FWingActorComponent
|
|||||||
bool IsNative() const { return NativeComponent != nullptr; }
|
bool IsNative() const { return NativeComponent != nullptr; }
|
||||||
bool IsOwnedBy(const UBlueprint* BP) const;
|
bool IsOwnedBy(const UBlueprint* BP) const;
|
||||||
|
|
||||||
|
// Attach ChildNode under this component. If this is an SCS node, uses
|
||||||
|
// 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
|
// Collect all components: native from CDO first, then SCS nodes
|
||||||
// walking the hierarchy from oldest ancestor to current blueprint.
|
// walking the hierarchy from oldest ancestor to current blueprint.
|
||||||
static TArray<FWingActorComponent> GetAll(UBlueprint* BP);
|
static TArray<FWingActorComponent> GetAll(UBlueprint* BP);
|
||||||
|
|||||||
@@ -17,6 +17,13 @@ struct FWingProperty
|
|||||||
FString GetText() const;
|
FString GetText() const;
|
||||||
bool SetText(const FString& Value);
|
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; }
|
explicit operator bool() const { return Prop != nullptr; }
|
||||||
FProperty* operator->() const { return Prop; }
|
FProperty* operator->() const { return Prop; }
|
||||||
|
|
||||||
@@ -27,6 +34,5 @@ struct FWingProperty
|
|||||||
static FWingProperty FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name);
|
static FWingProperty FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static void Collect(UStruct* StructType, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags);
|
static void Collect(UStruct* Struct, void* Container, TArray<FWingProperty> &Props, EPropertyFlags Flags);
|
||||||
static void Collect(UObject* 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(const FString& Name);
|
||||||
static FString SanitizeName(FName 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);
|
static FString FormatNodeTitle(const UEdGraphNode *Node);
|
||||||
|
|
||||||
// ----- Enum helpers -----
|
// ----- Enum helpers -----
|
||||||
|
|||||||
Reference in New Issue
Block a user