Work on blueprint components in MCP
This commit is contained in:
@@ -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,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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
|
||||
Reference in New Issue
Block a user