From 9812a4a41377ea0f8c7cb1ee451ae9b1d06e1ce2 Mon Sep 17 00:00:00 2001 From: jyelon Date: Thu, 19 Mar 2026 12:01:38 -0400 Subject: [PATCH] More refinement of Wing server --- .../UEWingman/HalfBaked/Blueprint_Reparent.h | 4 +- .../UEWingman/Handlers/Asset_ContentBrowse.h | 1 + .../Source/UEWingman/Handlers/Asset_Search.h | 1 + .../UEWingman/Handlers/GraphNode_Create.h | 2 +- .../Source/UEWingman/Handlers/ShowCommands.h | 2 +- .../UEWingman/Private/WingBlueprintVar.cpp | 12 +- .../UEWingman/Private/WingGraphExport.cpp | 19 +-- .../Source/UEWingman/Private/WingLogCapture.h | 1 + .../Source/UEWingman/Private/WingNotifier.cpp | 1 + .../Source/UEWingman/Private/WingServer.cpp | 2 +- .../Source/UEWingman/Private/WingUtils.cpp | 118 +----------------- .../UEWingman/Public/WingFunctionArgs.h | 2 +- .../Source/UEWingman/Public/WingGraphExport.h | 3 +- .../Source/UEWingman/Public/WingUtils.h | 39 ++++-- tools/clangd-diag-all-source.py | 8 +- 15 files changed, 60 insertions(+), 155 deletions(-) diff --git a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_Reparent.h b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_Reparent.h index 5505f664..414c8c1d 100644 --- a/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_Reparent.h +++ b/Plugins/UEWingman/Source/UEWingman/HalfBaked/Blueprint_Reparent.h @@ -43,7 +43,7 @@ public: FString OldParentName = BP->ParentClass ? WingUtils::FormatName(BP->ParentClass) : TEXT("None"); // Find the new parent class by short type name - UClass* NewParentClassObj = UWingTypes::TextToOneObjectType(NewParentClass); + UClass* NewParentClassObj = UWingTypes::TextToOneObjectType(Parent); if (!NewParentClassObj) return; // Perform reparent @@ -53,7 +53,5 @@ public: UWingServer::Printf(TEXT("Reparented %s: %s -> %s\n"), *WingUtils::FormatName(BP), *OldParentName, *WingUtils::FormatName(NewParentClassObj)); - if (!bSaved) - UWingServer::Print(TEXT("Warning: save failed\n")); } }; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_ContentBrowse.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_ContentBrowse.h index 96c95a4c..572a1ed7 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_ContentBrowse.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_ContentBrowse.h @@ -5,6 +5,7 @@ #include "WingHandler.h" #include "WingUtils.h" #include "AssetRegistry/AssetRegistryModule.h" +#include "AssetRegistry/AssetData.h" #include "AssetRegistry/IAssetRegistry.h" #include "Asset_ContentBrowse.generated.h" diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_Search.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_Search.h index 0f674c66..dfea0884 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_Search.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Asset_Search.h @@ -6,6 +6,7 @@ #include "WingUtils.h" #include "WingTypes.h" #include "AssetRegistry/AssetRegistryModule.h" +#include "AssetRegistry/AssetData.h" #include "AssetRegistry/IAssetRegistry.h" #include "Asset_Search.generated.h" diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h index 874f118d..84ada828 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/GraphNode_Create.h @@ -66,7 +66,7 @@ public: continue; // Find the action by exact full name - TArray> Matches = WingUtils::SearchGraphActions(TargetGraph, Entry.ActionName, 0, /*ExactMatch=*/true); + TArray> Matches = WingUtils::SearchGraphActions(TargetGraph, Entry.ActionName, 2, /*ExactMatch=*/true); if (Matches.Num() == 0) { UWingServer::Printf(TEXT("ERROR: No action found matching '%s'. Use GraphNodeSearchTypes to find available actions.\n"), diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/ShowCommands.h b/Plugins/UEWingman/Source/UEWingman/Handlers/ShowCommands.h index 7b91b5e4..b53c8886 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/ShowCommands.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/ShowCommands.h @@ -30,7 +30,7 @@ public: { if (Verbose) { - WingUtils::FormatCommandHelp(Class); + WingUtils::PrintHandlerHelp(Class); return; } UWingServer::Print(WingUtils::GetHandlerName(Class)); diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp index 77b25b67..30bc82d9 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingBlueprintVar.cpp @@ -8,20 +8,14 @@ FWingBlueprintVar::FWingBlueprintVar(UBlueprint* BP, const FString& VarName) { - FName VarFName(*VarName); - int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(BP, VarFName); - if (VarIndex == INDEX_NONE) - { - UWingServer::Printf(TEXT("ERROR: Variable '%s' not found in %s\n"), *VarName, *WingUtils::FormatName(BP)); - return; - } - Desc = &BP->NewVariables[VarIndex]; + Desc = WingUtils::FindExactlyOneNamed(VarName, BP->NewVariables); + if (!Desc) return; // Try to find the default value property on the CDO. if (BP->GeneratedClass) { UObject* CDO = BP->GeneratedClass->GetDefaultObject(); - FProperty* Prop = BP->GeneratedClass->FindPropertyByName(VarFName); + FProperty* Prop = BP->GeneratedClass->FindPropertyByName(Desc->VarName); if (CDO && Prop) DefaultValueProp = FWingProperty(Prop, CDO); } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp index db9aa4f4..a5731454 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingGraphExport.cpp @@ -1,4 +1,5 @@ #include "WingGraphExport.h" +#include "WingProperty.h" #include "WingTypes.h" #include "WingUtils.h" #include "Engine/Blueprint.h" @@ -263,19 +264,19 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node) } } -void WingGraphExport::EmitMaterialProperty(UMaterialExpression* Expression, FProperty* Prop, FStringBuilderBase& Out) +void WingGraphExport::EmitMaterialProperty(const FWingProperty& WP, FStringBuilderBase& Out) { - FString ValueStr = WingUtils::GetPropertyValueText(Expression, Prop); + 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 = !Prop->HasAnyPropertyFlags(CPF_EditConst); + bool bEditable = !WP->HasAnyPropertyFlags(CPF_EditConst); Out.Appendf(TEXT(" %s %s %s = %s\n"), bEditable ? TEXT("mxeditable") : TEXT("mxreadonly"), - *UWingTypes::TypeToText(Prop), - *WingUtils::FormatName(Prop), + *UWingTypes::TypeToText(WP.Prop), + *WingUtils::FormatName(WP.Prop), *ValueStr); } @@ -286,13 +287,13 @@ void WingGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderB UMaterialExpression* Expression = MatNode->MaterialExpression; FString PrimaryCategory = Expression->GetClass()->GetName(); - TArray Props = WingUtils::SearchProperties(Expression, FString(), CPF_Edit, false); + TArray Props = FWingProperty::GetAll(Expression, CPF_Edit); - for (FProperty* Prop : Props) + for (const FWingProperty& WP : Props) { - FString Category = Prop->HasMetaData(TEXT("Category")) ? Prop->GetMetaData(TEXT("Category")) : FString(); + FString Category = WP->HasMetaData(TEXT("Category")) ? WP->GetMetaData(TEXT("Category")) : FString(); if ((Category == PrimaryCategory) == bPrimary) - EmitMaterialProperty(Expression, Prop, Out); + EmitMaterialProperty(WP, Out); } } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingLogCapture.h b/Plugins/UEWingman/Source/UEWingman/Private/WingLogCapture.h index e21c97f2..d171d2a0 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingLogCapture.h +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingLogCapture.h @@ -1,6 +1,7 @@ #pragma once #include "CoreMinimal.h" +#include "Misc/OutputDeviceRedirector.h" class FLogCaptureOutputDevice : public FOutputDevice { diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp index fdd9ee4f..e8cb3cf6 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingNotifier.cpp @@ -1,4 +1,5 @@ #include "WingNotifier.h" +#include "Editor.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraph.h" #include "Engine/Blueprint.h" diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp index 3541c927..347880e6 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingServer.cpp @@ -319,7 +319,7 @@ void UWingServer::TryCallHandler(const FString &Line) if (!WingJson::PopulateFromJson(HandlerObj->GetClass(), HandlerObj.Get(), &*Request)) { UWingServer::Printf(TEXT("\nUsage:\n\n")); - WingUtils::FormatCommandHelp(*HandlerClass); + WingUtils::PrintHandlerHelp(*HandlerClass); return; } diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp index 2eb1134d..1a40f87d 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingUtils.cpp @@ -508,11 +508,7 @@ TArray> WingUtils::SearchGraphActions(UEdGraph* } // ============================================================ -// PopulateFromJson — fill a USTRUCT from a JSON object -// ============================================================ - -// ============================================================ -// CollectHandlerClasses — find all concrete IWingHandler classes +// Support for locating UE Wingman Handlers // ============================================================ TArray WingUtils::CollectHandlerClasses() @@ -529,26 +525,16 @@ TArray WingUtils::CollectHandlerClasses() return Result; } -// ============================================================ -// GetHandlerName — derive tool name from handler class name -// ============================================================ - FString WingUtils::GetHandlerName(UClass* HandlerClass) { FString Name = HandlerClass->GetName(); - // Strip "Wing_" prefix Name.RemoveFromStart(TEXT("Wing_")); return Name; } -// ============================================================ -// GetHandlerGroup — derive group name from handler class name -// ============================================================ - FString WingUtils::GetHandlerGroup(UClass* HandlerClass) { FString Name = HandlerClass->GetName(); - // Strip "Wing_" prefix Name.RemoveFromStart(TEXT("Wing_")); // Everything before the underscore is the group int32 UnderscoreIdx; @@ -558,108 +544,10 @@ FString WingUtils::GetHandlerGroup(UClass* HandlerClass) } // ============================================================ -// GetTemplate +// PrintHandlerHelp — verbose description of one handler command // ============================================================ -// ============================================================ -// FindPropertyByName -// ============================================================ - -FProperty* WingUtils::FindPropertyByName(UObject* Obj, const FString& Name) -{ - if (!Obj) - { - UWingServer::Print(TEXT("ERROR: Object is null\n")); - return nullptr; - } - - FProperty* Found = nullptr; - for (TFieldIterator PropIt(Obj->GetClass()); PropIt; ++PropIt) - { - if (!Identifies(Name, *PropIt)) continue; - if (Found) - { - UWingServer::Printf(TEXT("ERROR: Ambiguous property '%s' on %s\n"), *Name, *FormatName(Obj->GetClass())); - return nullptr; - } - Found = *PropIt; - } - - if (!Found) - UWingServer::Printf(TEXT("ERROR: Property '%s' not found on %s\n"), *Name, *FormatName(Obj->GetClass())); - - return Found; -} - -// ============================================================ -// GetPropertyValueText -// ============================================================ - -FString WingUtils::GetPropertyValueText(UObject* Container, FProperty* Prop) -{ - FString Result; - void* ValuePtr = Prop->ContainerPtrToValuePtr(Container); - Prop->ExportTextItem_Direct(Result, ValuePtr, nullptr, Container, PPF_None); - return Result; -} - -// ============================================================ -// SetPropertyValueText -// ============================================================ - -bool WingUtils::SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value) -{ - void* ValuePtr = Prop->ContainerPtrToValuePtr(Container); - const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Container, PPF_None); - if (!ImportResult) - { - UWingServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"), - *Value, *FormatName(Prop), *Prop->GetCPPType()); - return false; - } - return true; -} - -bool WingUtils::SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner) -{ - void* ValuePtr = Prop->ContainerPtrToValuePtr(Container); - const TCHAR* ImportResult = Prop->ImportText_Direct(*Value, ValuePtr, Owner, PPF_None); - if (!ImportResult) - { - UWingServer::Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"), - *Value, *FormatName(Prop), *Prop->GetCPPType()); - return false; - } - return true; -} - -// ============================================================ -// SearchProperties -// ============================================================ - -TArray WingUtils::SearchProperties(UObject* Obj, const FString& Query, EPropertyFlags Flags, bool bLocal) -{ - TArray Result; - if (!Obj) return Result; - UClass* ObjClass = Obj->GetClass(); - for (TFieldIterator PropIt(ObjClass); PropIt; ++PropIt) - { - FProperty* Prop = *PropIt; - if (!Prop) continue; - if (Flags != 0 && !Prop->HasAnyPropertyFlags(Flags)) continue; - if (bLocal && Prop->GetOwnerStruct() != ObjClass) continue; - if (!Query.IsEmpty() && !FormatName(Prop).Contains(Query, ESearchCase::IgnoreCase)) - continue; - Result.Add(Prop); - } - return Result; -} - -// ============================================================ -// FormatCommandHelp — verbose description of one handler command -// ============================================================ - -void WingUtils::FormatCommandHelp(UClass* HandlerClass) +void WingUtils::PrintHandlerHelp(UClass* HandlerClass) { const IWingHandler* Handler = Cast(HandlerClass->GetDefaultObject()); if (!Handler) return; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingFunctionArgs.h b/Plugins/UEWingman/Source/UEWingman/Public/WingFunctionArgs.h index b55ec481..936326d3 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingFunctionArgs.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingFunctionArgs.h @@ -1,10 +1,10 @@ #pragma once #include "CoreMinimal.h" +#include "EdGraph/EdGraphPin.h" class UEdGraphNode; class UK2Node_EditablePinBase; -struct FEdGraphPinType; struct WingFunctionArgs { diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h b/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h index 5e6415ce..2220f96c 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingGraphExport.h @@ -7,6 +7,7 @@ #include "EdGraph/EdGraphPin.h" class UMaterialExpression; +struct FWingProperty; class WingGraphExport { @@ -63,7 +64,7 @@ private: void Traverse(UEdGraphNode* Node); void SortNodes(); void EmitNode(UEdGraphNode* Node); - void EmitMaterialProperty(UMaterialExpression* Expression, FProperty* Prop, FStringBuilderBase& Out); + void EmitMaterialProperty(const FWingProperty& WP, FStringBuilderBase& Out); void EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary); void EmitLocalVariables(); void EmitGraph(); diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index e39108c8..8e0caa7f 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -100,6 +100,14 @@ public: return Result; } + template + static TArray FindAllNamed(const FString &Name, TArray &Array) + { + TArray Result; + for (T& Elt : Array) if (Identifies(Name, Elt)) Result.Add(&Elt); + return Result; + } + template static T* FindExactlyOneNamed(const FString &Name, const TArray &Array) { @@ -110,6 +118,16 @@ public: return Result; } + template + static T* FindExactlyOneNamed(const FString &Name, TArray &Array) + { + int Count = 0; + T* Result = nullptr; + for (T& Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = &Elt; } + if (!CheckExactlyOneNamed(Count, T::StaticStruct()->GetName(), Name)) return nullptr; + return Result; + } + template static bool FindExactlyNoneNamed(const FString &Name, const TArray &Array) { @@ -120,6 +138,16 @@ public: return true; } + template + static bool FindExactlyNoneNamed(const FString &Name, const TArray &Array) + { + for (const T& Elt: Array) if (Identifies(Name, Elt)) + { + return CheckExactlyNoneNamed(1, T::StaticStruct()->GetName(), Name); + } + return true; + } + //////////////////////////////////////////////////////// static void SanitizeNameInPlace(FString& Name); @@ -181,13 +209,6 @@ public: static FString ActionFullName(const TSharedPtr& Action); static TArray> SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults = 0, bool ExactMatch = false); - // ----- Editable template ----- - static TArray SearchProperties(UObject* Obj, const FString& Query, EPropertyFlags Flags, bool bLocal); - static FProperty* FindPropertyByName(UObject* Obj, const FString& Name); - static FString GetPropertyValueText(UObject* Container, FProperty* Prop); - static bool SetPropertyValueText(UObject* Container, FProperty* Prop, const FString& Value); - static bool SetPropertyValueText(void* Container, FProperty* Prop, const FString& Value, UObject* Owner); - // ----- Text formatting ----- static FString WrapText(const FString& Text, int32 ColLimit, const FString& Prefix); @@ -195,7 +216,7 @@ public: static TArray CollectHandlerClasses(); static FString GetHandlerName(UClass* HandlerClass); static FString GetHandlerGroup(UClass* HandlerClass); - static void FormatCommandHelp(UClass* HandlerClass); + static void PrintHandlerHelp(UClass* HandlerClass); // ----- Common Error Reporting ----- static bool CheckExactlyOneNamed(int Count, const FString &Kind, const FString &Name); @@ -203,7 +224,5 @@ public: static bool CheckExactlyNoneNamed(int Count, const FString &Kind, const FString &Name); static bool CheckExactlyNoneNamed(int Count, UClass *Class, const FString &Name); -private: - static void AppendNumericSuffix(FString &Name, int32 N); }; diff --git a/tools/clangd-diag-all-source.py b/tools/clangd-diag-all-source.py index 4c57186f..fdc492b9 100755 --- a/tools/clangd-diag-all-source.py +++ b/tools/clangd-diag-all-source.py @@ -16,10 +16,10 @@ SOURCE_DIRS = [ "luprex/cpp/core", "luprex/cpp/drv", "luprex/cpp/wrap", - "Plugins/BlueprintMCP/Source/BlueprintMCP/Public", - "Plugins/BlueprintMCP/Source/BlueprintMCP/Private", - "Plugins/BlueprintMCP/Source/BlueprintMCP/Handlers", - "Plugins/BlueprintMCP/Source/BlueprintMCP/HalfBaked", + "Plugins/UEWingman/Source/UEWingman/Public", + "Plugins/UEWingman/Source/UEWingman/Private", + "Plugins/UEWingman/Source/UEWingman/Handlers", + "Plugins/UEWingman/Source/UEWingman/HalfBaked", ] # Files to skip (relative to project root).