diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump2.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump2.h index 352f59b9..a632969a 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump2.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump2.h @@ -34,7 +34,7 @@ public: if (!Target) return; WingPropHandle Props; - WingPropHandle::Handles Handles = Props.GetDetails(Target, false, CPF_Edit); + WingPropHandle::Handles Handles = Props.GetDetails(Target, false); // Sort by category name for consistent grouping. Handles.Sort([](const TSharedPtr& A, const TSharedPtr& B) diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp index 8a9fa4b5..2a4a7f5f 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp @@ -1,5 +1,4 @@ #include "WingGraphExport.h" -#include "WingProperty.h" #include "WingTypes.h" #include "WingUtils.h" #include "Engine/Blueprint.h" @@ -204,8 +203,8 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node) Output.Appendf(TEXT("\nnode %s: %s\n"), *WingUtils::FormatName(Node), *WingUtils::FormatNodeTitle(Node)); - // Emit material expression properties (if applicable). - EmitMaterialProperties(Node, Output, true); + // Emit node properties (if applicable). + EmitNodeProperties(Node, Output, true); // Emit input data pins. for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input)) @@ -249,36 +248,26 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node) } } -void WingGraphExport::EmitMaterialProperty(const FWingProperty& WP, FStringBuilderBase& Out) +void WingGraphExport::EmitNodeProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary) { - FString ValueStr = WP.GetText(); - ValueStr.ReplaceInline(TEXT("\r\n"), TEXT(" ")); - ValueStr.ReplaceInline(TEXT("\n"), TEXT(" ")); - if (ValueStr.Len() > 80) - ValueStr = ValueStr.Left(80) + TEXT("..."); - bool bEditable = !WP->HasAnyPropertyFlags(CPF_EditConst); - Out.Appendf(TEXT(" %s %s %s = %s\n"), - bEditable ? TEXT("mxeditable") : TEXT("mxreadonly"), - *UWingTypes::TypeToText(WP.Prop), - *WingUtils::FormatName(WP.Prop), - *ValueStr); -} - -void WingGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary) -{ - UMaterialGraphNode* MatNode = Cast(Node); - if (!MatNode || !MatNode->MaterialExpression) return; - - UMaterialExpression* Expression = MatNode->MaterialExpression; - FString PrimaryCategory = Expression->GetClass()->GetName(); - TArray Props = FWingProperty::GetDetailsMutable(Expression, CPF_Edit); - - for (const FWingProperty& WP : Props) + FString PrimaryCategory; + if (UMaterialGraphNode* MatNode = Cast(Node)) { - FString Category = WP->HasMetaData(TEXT("Category")) ? WP->GetMetaData(TEXT("Category")) : FString(); - if ((Category == PrimaryCategory) == bPrimary) - EmitMaterialProperty(WP, Out); + WingPropHandle::Handles Handles = Props.GetDetails(Node, false); + PrimaryCategory = MatNode->MaterialExpression->GetClass()->GetName(); + for (const TSharedPtr& H : Handles) + { + FString Category = H->GetProperty()->GetMetaData(TEXT("Category")); + if ((Category == PrimaryCategory) == bPrimary) + WingPropHandle::Print(*H, Out); + } + } + else if (bPrimary) + { + WingPropHandle::Handles Handles = Props.GetDetails(Node, false); + for (const TSharedPtr& H : Handles) + WingPropHandle::Print(*H, Out); } } @@ -305,9 +294,9 @@ void WingGraphExport::EmitDetails() { if (Node->IsA()) continue; Details.Appendf(TEXT("\ndetails %s\n"), *WingUtils::FormatName(Node)); - Details.Appendf(TEXT(" pos %d, %d\n"), Node->NodePosX, Node->NodePosY); + Details.Appendf(TEXT(" pos %d, %d\n"), Node->NodePosX, Node->NodePosY); - EmitMaterialProperties(Node, Details, false); + EmitNodeProperties(Node, Details, false); } } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp index 3f0dfa12..72b2b8c5 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp @@ -127,7 +127,7 @@ WingPropHandle::FlatTree WingPropHandle::AllTreeNodes(Root& Root) // ///////////////////////////////////////////////////////////////////////////// -WingPropHandle::Handles WingPropHandle::AllProperties(Root& Root, EPropertyFlags Filter) +WingPropHandle::Handles WingPropHandle::AllProperties(Root& Root, bool RootFilter, EPropertyFlags Filter) { Handles Result; for (IDetailTreeNode* Node : AllTreeNodes(Root)) @@ -137,7 +137,7 @@ WingPropHandle::Handles WingPropHandle::AllProperties(Root& Root, EPropertyFlags { if (Filter != CPF_None && !Handle->GetProperty()->HasAllPropertyFlags(Filter)) continue; - if (!IsInsideRootObject(Root, *Handle)) + if (RootFilter && !IsInsideRootObject(Root, *Handle)) continue; Result.Add(Handle); } @@ -145,16 +145,16 @@ WingPropHandle::Handles WingPropHandle::AllProperties(Root& Root, EPropertyFlags return Result; } -WingPropHandle::Handles WingPropHandle::AllProperties(UObject* Obj, EPropertyFlags Filter) +WingPropHandle::Handles WingPropHandle::AllProperties(UObject* Obj, bool RootFilter, EPropertyFlags Filter) { if (!Obj) return {}; - return AllProperties(GetRootForObject(Obj), Filter); + return AllProperties(GetRootForObject(Obj), RootFilter, Filter); } -WingPropHandle::Handles WingPropHandle::AllProperties(const UStruct* ScriptStruct, uint8* Data, EPropertyFlags Filter) +WingPropHandle::Handles WingPropHandle::AllProperties(const UStruct* ScriptStruct, uint8* Data, bool RootFilter, EPropertyFlags Filter) { if (!ScriptStruct || !Data) return {}; - return AllProperties(GetRootForStruct(ScriptStruct, Data), Filter); + return AllProperties(GetRootForStruct(ScriptStruct, Data), RootFilter, Filter); } ///////////////////////////////////////////////////////////////////////////// @@ -163,28 +163,28 @@ WingPropHandle::Handles WingPropHandle::AllProperties(const UStruct* ScriptStruc // ///////////////////////////////////////////////////////////////////////////// -TSharedPtr WingPropHandle::NamedProperty(Root& Root, FName Name) +TSharedPtr WingPropHandle::NamedProperty(Root& Root, FName Name, bool RootFilter) { for (IDetailTreeNode* Node : AllTreeNodes(Root)) { if (Node->GetNodeName() != Name) continue; TSharedPtr Handle = Node->CreatePropertyHandle(); - if (Handle.IsValid() && IsInsideRootObject(Root, *Handle)) + if (Handle.IsValid() && (!RootFilter || IsInsideRootObject(Root, *Handle))) return Handle; } return nullptr; } -TSharedPtr WingPropHandle::NamedProperty(UObject* Obj, FName Name) +TSharedPtr WingPropHandle::NamedProperty(UObject* Obj, FName Name, bool RootFilter) { if (!Obj) return nullptr; - return NamedProperty(GetRootForObject(Obj), Name); + return NamedProperty(GetRootForObject(Obj), Name, RootFilter); } -TSharedPtr WingPropHandle::NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name) +TSharedPtr WingPropHandle::NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter) { if (!ScriptStruct || !Data) return nullptr; - return NamedProperty(GetRootForStruct(ScriptStruct, Data), Name); + return NamedProperty(GetRootForStruct(ScriptStruct, Data), Name, RootFilter); } ///////////////////////////////////////////////////////////////////////////// @@ -193,8 +193,11 @@ TSharedPtr WingPropHandle::NamedProperty(const UStruct* ScriptS // ///////////////////////////////////////////////////////////////////////////// -WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable, EPropertyFlags Filter) +WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable) { + bool RootFilter = false; + EPropertyFlags PropFlags = CPF_Edit; + if (!Obj) return {}; // Blueprints: redirect to the generated class CDO. @@ -208,7 +211,11 @@ WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable, E Obj = BP->GeneratedClass->GetDefaultObject(); } - // Component references: redirect to the template. + // UWingComponentReference is a class of our own creation, containing + // a pointer to a blueprint and a component name. Sometimes, the + // component is inherited. If so, and if you want to mutate it, you + // first have to create the override template in the child. If you're + // not mutating, you can just use the existing inherited template. if (UWingComponentReference* Ref = Cast(Obj)) { Obj = Mutable ? Ref->GetMutableTemplate() : Ref->GetImmutableTemplate(); @@ -219,29 +226,34 @@ WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable, E } } - Handles Result = AllProperties(Obj, Filter); + // Actors have components, which flood the property listing with hundreds + // of confusing additional properties. + if (Cast(Obj)) RootFilter = true; + // Fetch the handles. + Handles Result = AllProperties(Obj, RootFilter, PropFlags); + // Material graph nodes: also collect expression properties. if (UMaterialGraphNode* MatNode = Cast(Obj)) { if (UMaterialExpression* Expr = MatNode->MaterialExpression) { - Result.Append(AllProperties(Expr, Filter)); + Result.Append(AllProperties(Expr, true, CPF_Edit)); } } // Widgets: hide the Slot property, add slot properties. - if (UWidget* Widget = Cast(Obj)) - { - Result.RemoveAll([](const TSharedPtr& H) - { - return H->GetProperty()->GetFName() == TEXT("Slot"); - }); - if (UPanelSlot* Slot = Widget->Slot) - { - Result.Append(AllProperties(Slot, Filter)); - } - } + // if (UWidget* Widget = Cast(Obj)) + // { + // Result.RemoveAll([](const TSharedPtr& H) + // { + // return H->GetProperty()->GetFName() == TEXT("Slot"); + // }); + // if (UPanelSlot* Slot = Widget->Slot) + // { + // Result.Append(AllProperties(Slot, false, CPF_Edit)); + // } + // } return Result; } @@ -326,3 +338,27 @@ bool WingPropHandle::SetText(IPropertyHandle& Handle, const FString& Text) } return true; } + +///////////////////////////////////////////////////////////////////////////// +// +// Print +// +///////////////////////////////////////////////////////////////////////////// + +void WingPropHandle::Print(IPropertyHandle& Handle, FStringBuilderBase& Out) +{ + FProperty* Prop = Handle.GetProperty(); + + FString Value = GetText(Handle); + Value.ReplaceInline(TEXT("\r"), TEXT(" ")); + Value.ReplaceInline(TEXT("\n"), TEXT(" ")); + if (Value.Len() > 100) Value = Value.Left(100) + TEXT("..."); + + bool bEditable = !Handle.IsEditConst(); + + Out.Appendf(TEXT(" %s %s %s = %s\n"), + bEditable ? TEXT("editable") : TEXT("readonly"), + *UWingTypes::TypeToText(Prop), + *WingUtils::FormatName(Prop), + *Value); +} diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h b/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h index cb9e80c7..0b1f8b51 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingActorComponent.h @@ -23,13 +23,13 @@ public: UPROPERTY() UBlueprint* BP = nullptr; - // The raw component name, not sanitized yet. + // The component name. FName VariableName; - // Sanitized Parent Name (for display only) + // Externalized Parent Name (for display only) FString ParentName; - // Sanitized TypeName of the component (for display only) + // Externalized TypeName of the component (for display only) FString TypeName; // True if the component is native (for display only) diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h b/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h index 2220f96c..2c89345c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h @@ -1,14 +1,12 @@ #pragma once #include "CoreMinimal.h" +#include "WingPropHandle.h" #include "Engine/Blueprint.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" -class UMaterialExpression; -struct FWingProperty; - class WingGraphExport { public: @@ -64,8 +62,7 @@ private: void Traverse(UEdGraphNode* Node); void SortNodes(); void EmitNode(UEdGraphNode* Node); - void EmitMaterialProperty(const FWingProperty& WP, FStringBuilderBase& Out); - void EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary); + void EmitNodeProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary); void EmitLocalVariables(); void EmitGraph(); void EmitDetails(); @@ -79,6 +76,7 @@ private: UEdGraph* Graph; + WingPropHandle Props; // Data populated by passes. TArray SortedNodes; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h b/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h index bf5b08f1..6453bfb2 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingPropHandle.h @@ -4,15 +4,16 @@ #include "IPropertyRowGenerator.h" #include "PropertyHandle.h" -// WingPropHandle: A wrapper around IPropertyRowGenerator that -// provides easy access to IPropertyHandle objects. +// WingPropHandle: A module that provides easy access to +// IPropertyHandle objects. To use this module, construct a +// WingPropHandle, then use it to fetch properties from +// objects. The property handles returned are valid until +// the WingPropHandle is destroyed. // -// Usage: -// WingPropHandle Props; -// TArray> Handles = Props.AddObject(MyObject); +// In UE Wingman, we are encouraging the use of +// IPropertyHandle over FProperty. The FProperty API sucks: +// it's buggy and unreliable. Use IPropertyHandle instead. // -// The generators and all handles are valid for the lifetime of -// this object. class WingPropHandle { @@ -24,24 +25,28 @@ public: // Get all properties of a UObject. Returns the handles. // Only properties that have all the specified flags are included. - Handles AllProperties(UObject* Obj, EPropertyFlags Filter = CPF_None); + // If RootFilter is true, only properties inside the root object are returned. + Handles AllProperties(UObject* Obj, bool RootFilter, EPropertyFlags Filter); // Get all properties of a struct. Does not copy — the data // pointer must remain valid for the lifetime of this object. // Only properties that have all the specified flags are included. - Handles AllProperties(const UStruct* ScriptStruct, uint8* Data, EPropertyFlags Filter = CPF_None); + // If RootFilter is true, only properties inside the root object are returned. + Handles AllProperties(const UStruct* ScriptStruct, uint8* Data, bool RootFilter, EPropertyFlags Filter); + + // Get a single named property from a UObject. + // If RootFilter is true, only properties inside the root object are returned. + TSharedPtr NamedProperty(UObject* Obj, FName Name, bool RootFilter); + + // Get a single named property from a struct. + // If RootFilter is true, only properties inside the root object are returned. + TSharedPtr NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter); // Get "details panel" properties for an object. Handles special // cases: blueprints (redirects to CDO), component references // (redirects to template), material graph nodes (includes // expression properties), widgets (includes slot properties). - Handles GetDetails(UObject* Obj, bool Mutable, EPropertyFlags Filter = CPF_None); - - // Get a single named property from a UObject. - TSharedPtr NamedProperty(UObject* Obj, FName Name); - - // Get a single named property from a struct. - TSharedPtr NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name); + Handles GetDetails(UObject* Obj, bool Mutable); // Get/set text with special handling for enums (smarter prefix // matching via WingUtils::StringToEnum) and FEdGraphPinType @@ -49,6 +54,10 @@ public: static FString GetText(IPropertyHandle& Handle); static bool SetText(IPropertyHandle& Handle, const FString& Text); + // Print a single property in a standardized format: + // editable|readonly Type Name = Value + static void Print(IPropertyHandle& Handle, FStringBuilderBase& Out); + private: struct Root { @@ -68,6 +77,6 @@ private: static void AllTreeNodesRecursive(const TSharedRef& Node, FlatTree& Out); static FlatTree AllTreeNodes(Root& Root); - Handles AllProperties(Root& Root, EPropertyFlags Filter); - static TSharedPtr NamedProperty(Root& Root, FName Name); + Handles AllProperties(Root& Root, bool RootFilter, EPropertyFlags Filter); + static TSharedPtr NamedProperty(Root& Root, FName Name, bool RootFilter); };