diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Dump.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Dump.h new file mode 100644 index 00000000..4b9b7962 --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Dump.h @@ -0,0 +1,59 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingHandler.h" +#include "WingFetcher.h" +#include "WingPropHandle.h" +#include "WingUtils.h" +#include "Details_Dump.generated.h" + +UCLASS() +class UWing_Details_Dump : public UWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Target object")) + FString Object; + + UPROPERTY(meta=(Optional, Description="Substring filter for property names")) + FString Query; + + virtual void Register() override + { + UWingServer::AddHandler(this, + TEXT("Test handler: dump properties using IPropertyHandle.")); + } + + virtual void Handle() override + { + WingFetcher F; + UObject* Target = F.Walk(Object).Cast(); + if (!Target) return; + + WingPropHandle Props; + WingPropHandle::Handles Handles = Props.GetDetails(Target, false); + + // Group by category, preserving within-category order. + FString QueryLower = Query.ToLower(); + TSortedMap>> Categories; + for (const TSharedPtr& H : Handles) + { + FString Name = WingUtils::FormatName(H); + if (!QueryLower.IsEmpty() && !Name.ToLower().Contains(QueryLower)) + continue; + FString Category = H->GetDefaultCategoryName().ToString(); + if (Category.IsEmpty()) Category = TEXT("Unclassified"); + Categories.FindOrAdd(Category).Add(H); + } + + FStringBuilderBase& Out = UWingServer::GetPrintBuffer(); + for (const auto& Pair : Categories) + { + Out.Appendf(TEXT("\n%s:\n"), *Pair.Key); + for (const TSharedPtr& H : Pair.Value) + WingPropHandle::Print(*H, Out); + } + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get2.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Get.h similarity index 90% rename from Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get2.h rename to Plugins/UEWingman/Source/UEWingman/Handlers/Details_Get.h index d6562757..c22671e3 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Get2.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Get.h @@ -6,10 +6,10 @@ #include "WingFetcher.h" #include "WingPropHandle.h" #include "WingUtils.h" -#include "Property_Get2.generated.h" +#include "Details_Get.generated.h" UCLASS() -class UWing_Property_Get2 : public UWingHandler +class UWing_Details_Get : public UWingHandler { GENERATED_BODY() diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Set.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Set.h new file mode 100644 index 00000000..e6de584a --- /dev/null +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_Set.h @@ -0,0 +1,46 @@ +#pragma once + +#include "CoreMinimal.h" +#include "WingServer.h" +#include "WingHandler.h" +#include "WingFetcher.h" +#include "WingPropHandle.h" +#include "WingUtils.h" +#include "Details_Set.generated.h" + +UCLASS() +class UWing_Details_Set : public UWingHandler +{ + GENERATED_BODY() + +public: + UPROPERTY(meta=(Description="Target object")) + FString Object; + + UPROPERTY(meta=(Description="Property name")) + FString Property; + + UPROPERTY(meta=(Description="New value in Unreal text format")) + FString Value; + + virtual void Register() override + { + UWingServer::AddHandler(this, + TEXT("Set a single editable property. Value uses Unreal text format.")); + } + + virtual void Handle() override + { + WingFetcher F; + UObject* Obj = F.Walk(Object).Cast(); + if (!Obj) return; + + WingPropHandle Props; + WingPropHandle::Handles Handles = Props.GetDetails(Obj, true); + TSharedPtr* P = WingUtils::FindOneWithExternalID(Property, Handles, TEXT("Property")); + if (!P) return; + + if (WingPropHandle::SetText(**P, Value)) + UWingServer::Print(TEXT("OK\n")); + } +}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set2.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_SetMany.h similarity index 94% rename from Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set2.h rename to Plugins/UEWingman/Source/UEWingman/Handlers/Details_SetMany.h index 2844d159..ffc7f249 100644 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Set2.h +++ b/Plugins/UEWingman/Source/UEWingman/Handlers/Details_SetMany.h @@ -6,10 +6,10 @@ #include "WingFetcher.h" #include "WingPropHandle.h" #include "WingUtils.h" -#include "Property_Set2.generated.h" +#include "Details_SetMany.generated.h" UCLASS() -class UWing_Property_Set2 : public UWingHandler +class UWing_Details_SetMany : public UWingHandler { GENERATED_BODY() diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_Create.h b/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_Create.h deleted file mode 100644 index aec4b5cc..00000000 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/MaterialInstance_Create.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "WingServer.h" -#include "WingHandler.h" -#include "WingFetcher.h" -#include "WingUtils.h" -#include "Materials/Material.h" -#include "Materials/MaterialInterface.h" -#include "Materials/MaterialInstanceConstant.h" -#include "Factories/MaterialInstanceConstantFactoryNew.h" -#include "WingPackageMaker.h" -#include "MaterialInstance_Create.generated.h" - - -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- -// --------------------------------------------------------------------------- - -UCLASS() -class UWing_MaterialInstance_Create : public UWingHandler -{ - GENERATED_BODY() - -public: - UPROPERTY(meta=(Description="Full asset path for the new Material Instance (e.g. '/Game/Materials/MI_GoldShiny')")) - FString AssetPath; - - UPROPERTY(meta=(Description="Parent material package path (Material or Material Instance)")) - FString ParentMaterial; - - virtual void Register() override - { - UWingServer::AddHandler(this, - TEXT("Create a new Material Instance Constant asset with a specified parent material.")); - } - virtual void Handle() override - { - WingPackageMaker Maker(AssetPath); - if (!Maker.Ok()) return; - - // Load parent material by package path. - WingFetcher F; - UObject* ParentObj = F.Asset(ParentMaterial).GetObj(); - UMaterialInterface* ParentMaterialObj = ParentObj ? Cast(ParentObj) : nullptr; - if (!ParentMaterialObj) - { - UWingServer::Printf(TEXT("ERROR: Parent material '%s' not found or not a material\n"), *ParentMaterial); - return; - } - - // Create via factory + AssetTools. - UMaterialInstanceConstant* MI = Maker.CreateAsset(); - if (!MI) return; - - // Set parent. - MI->Parent = ParentMaterialObj; - - UWingServer::Printf(TEXT("Created %s\n"), *MI->GetPathName()); - if (UMaterialInstance* ParentMI = Cast(ParentMaterialObj)) - UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMI)); - else if (UMaterial* ParentMat = Cast(ParentMaterialObj)) - UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMat)); - else - UWingServer::Printf(TEXT("Parent: %s\n"), *ParentMaterialObj->GetPathName()); - } -}; diff --git a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump2.h b/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump2.h deleted file mode 100644 index a632969a..00000000 --- a/Plugins/UEWingman/Source/UEWingman/Handlers/Property_Dump2.h +++ /dev/null @@ -1,78 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "WingServer.h" -#include "WingHandler.h" -#include "WingFetcher.h" -#include "WingPropHandle.h" -#include "WingUtils.h" -#include "WingTypes.h" -#include "Property_Dump2.generated.h" - -UCLASS() -class UWing_Property_Dump2 : public UWingHandler -{ - GENERATED_BODY() - -public: - UPROPERTY(meta=(Description="Target object")) - FString Object; - - UPROPERTY(meta=(Optional, Description="Substring filter for property names")) - FString Query; - - virtual void Register() override - { - UWingServer::AddHandler(this, - TEXT("Test handler: dump properties using IPropertyHandle.")); - } - - virtual void Handle() override - { - WingFetcher F; - UObject* Target = F.Walk(Object).Cast(); - if (!Target) return; - - WingPropHandle Props; - WingPropHandle::Handles Handles = Props.GetDetails(Target, false); - - // Sort by category name for consistent grouping. - Handles.Sort([](const TSharedPtr& A, const TSharedPtr& B) - { - return A->GetProperty()->GetMetaData(TEXT("Category")) < B->GetProperty()->GetMetaData(TEXT("Category")); - }); - - FString QueryLower = Query.ToLower(); - FString CurrentCategory; - - for (const TSharedPtr& H : Handles) - { - FProperty* Prop = H->GetProperty(); - FString Name = WingUtils::FormatName(Prop); - if (!QueryLower.IsEmpty() && !Name.ToLower().Contains(QueryLower)) - continue; - - FString Category = Prop->GetMetaData(TEXT("Category")); - if (Category.IsEmpty()) Category = TEXT("Unclassified"); - if (Category != CurrentCategory) - { - if (!CurrentCategory.IsEmpty()) - UWingServer::Print(TEXT("\n")); - CurrentCategory = Category; - UWingServer::Printf(TEXT("%s:\n"), *CurrentCategory); - } - - FString Value; - H->GetValueAsFormattedString(Value); - if (Value.Len() > 100) { Value.LeftInline(100); Value += TEXT("..."); } - - bool bEditable = !H->IsEditConst(); - - UWingServer::Printf(TEXT(" %s %s %s = %s\n"), - bEditable ? TEXT("editable") : TEXT("readonly"), - *UWingTypes::TypeToText(Prop), - *Name, - *Value); - } - } -}; diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingPackageMaker.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingPackageMaker.cpp deleted file mode 100644 index 11705dcf..00000000 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingPackageMaker.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include "WingPackageMaker.h" -#include "PackageTools.h" -#include "AssetRegistry/AssetRegistryModule.h" - -WingPackageMaker::WingPackageMaker(const FString& InFullPath) - : FullPath(InFullPath) -{ - if (!CheckNewAssetPath(InFullPath)) - { - bError = true; - return; - } -} - -bool WingPackageMaker::CheckNewAssetPath(const FString& Path) -{ - if (UPackageTools::SanitizePackageName(Path) != Path) - { - UWingServer::Printf(TEXT("ERROR: Package path '%s' is not a valid package name\n"), *Path); - return false; - } - if (!FPackageName::IsValidTextForLongPackageName(Path)) - { - UWingServer::Printf(TEXT("ERROR: Package path '%s' is not a valid package name\n"), *Path); - return false; - } - if (!Path.StartsWith(TEXT("/Game"))) - { - UWingServer::Printf(TEXT("ERROR: Package path '%s' must start with '/Game'\n"), *Path); - return false; - } - if (FindObject(nullptr, *Path)) - { - UWingServer::Printf(TEXT("ERROR: An asset already exists at '%s'\n"), *Path); - return false; - } - return true; -} - - - -bool WingPackageMaker::Make() -{ - if (bError) return false; - Pkg = CreatePackage(*FullPath); - if (!Pkg) - { - UWingServer::Printf(TEXT("ERROR: Failed to create package at '%s'\n"), *FullPath); - bError = true; - return false; - } - - Pkg->ClearFlags(RF_Transient); - Pkg->SetIsExternallyReferenceable(true); - Pkg->MarkPackageDirty(); - return true; -} - diff --git a/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp b/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp index cd1bf352..d1e033d5 100644 --- a/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp +++ b/Plugins/UEWingman/Source/UEWingman/Private/WingPropHandle.cpp @@ -283,6 +283,17 @@ FString WingPropHandle::GetText(IPropertyHandle& Handle) return UWingTypes::TypeToText(*static_cast(Data)); } + // Class properties: use our human-readable type names. + if (CastField(Handle.GetProperty())) + { + UObject* ClassObj = nullptr; + if (Handle.GetValue(ClassObj) != FPropertyAccess::Success) + UE_LOG(LogTemp, Fatal, TEXT("GetValue failed for class property '%s'"), + *Handle.GetProperty()->GetName()); + if (!ClassObj) return TEXT("None"); + return UWingTypes::TypeToText(ClassObj); + } + FString Result; if (Handle.GetValueAsFormattedString(Result) != FPropertyAccess::Success) UE_LOG(LogTemp, Fatal, TEXT("GetValueAsFormattedString failed for property '%s'"), @@ -316,6 +327,28 @@ bool WingPropHandle::SetText(IPropertyHandle& Handle, const FString& Text) return true; } + // Class properties: parse with our type parser. + if (FClassProperty* ClassProp = CastField(Prop)) + { + UObject* Class = nullptr; + if (!Text.IsEmpty()) + { + UWingTypes::Requirements Req; + Req.BlueprintType = true; + Req.Blueprintable = false; + Req.IsChildOf = ClassProp->MetaClass; + Class = UWingTypes::TextToOneObjectType(Text, Req); + if (!Class) return false; + } + if (Handle.SetValue(Class) != FPropertyAccess::Success) + { + UWingServer::Printf(TEXT("ERROR: Failed to set class property '%s'\n"), + *WingUtils::FormatName(Prop)); + return false; + } + return true; + } + // Enums: canonicalize the string with our smarter prefix matching. FString Value = Text; UEnum* Enum = nullptr; @@ -385,8 +418,6 @@ bool WingPropHandle::SetJson(IPropertyHandle& Handle, const TSharedPtrGetFName()); } +FString WingUtils::FormatName(IPropertyHandle &Handle) +{ + return ExternalizeID(Handle.GetProperty()->GetFName()); +} + +FString WingUtils::FormatName(const TSharedPtr &Handle) +{ + return FormatName(*Handle); +} + FString WingUtils::FormatName(const FUserPinInfo &Pin) { return ExternalizeID(Pin.PinName); diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingPackageMaker.h b/Plugins/UEWingman/Source/UEWingman/Public/WingPackageMaker.h deleted file mode 100644 index 901e8947..00000000 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingPackageMaker.h +++ /dev/null @@ -1,47 +0,0 @@ -#pragma once - -#include "CoreMinimal.h" -#include "WingServer.h" -#include "UObject/Package.h" -#include "AssetToolsModule.h" -#include "IAssetTools.h" - -// Helper for creating new asset packages. Validates the path and checks for -// conflicting assets in the constructor. Call Ok() to check, then Make() to -// actually create the UPackage. -class WingPackageMaker -{ -public: - WingPackageMaker(const FString& InFullPath); - - bool Ok() const { return !bError; } - bool Make(); - UPackage* Package() const { return Pkg; } - FString GetName() const { return FPackageName::GetShortName(FullPath); } - FName GetFName() const { return FName(GetName()); } - - template - AssetClass* CreateAsset() - { - if (bError) return nullptr; - IAssetTools& AssetTools = FModuleManager::LoadModuleChecked("AssetTools").Get(); - FactoryClass* Factory = NewObject(); - FString PkgPath = FPackageName::GetLongPackagePath(FullPath); - FString AssetName = FPackageName::GetShortName(FullPath); - UObject* NewAsset = AssetTools.CreateAsset(AssetName, PkgPath, AssetClass::StaticClass(), Factory); - AssetClass* Result = Cast(NewAsset); - if (!Result) - { - UWingServer::Printf(TEXT("ERROR: Failed to create asset at '%s'\n"), *FullPath); - bError = true; - } - return Result; - } - -private: - FString FullPath; - UPackage* Pkg = nullptr; - bool bError = false; - - static bool CheckNewAssetPath(const FString& Path); -}; diff --git a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h index b854d4ba..5cb345a0 100644 --- a/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h +++ b/Plugins/UEWingman/Source/UEWingman/Public/WingUtils.h @@ -20,6 +20,7 @@ struct FEdGraphSchemaAction; class UAnimationStateMachineGraph; class UAnimStateNode; class UAnimStateTransitionNode; +class IPropertyHandle; class UScriptStruct; class UEnum; struct FBPInterfaceDescription; @@ -186,6 +187,8 @@ public: static FString FormatName(const UScriptStruct *Struct); static FString FormatName(const UEnum *Enum); static FString FormatName(const FProperty *Prop); + static FString FormatName(IPropertyHandle &Handle); + static FString FormatName(const TSharedPtr &Handle); static FString FormatName(const FUserPinInfo &Pin); static FString FormatName(const FBPInterfaceDescription &IFace); static FString FormatName(const UWingComponentReference *Ref); diff --git a/Plugins/UEWingman/ue-wingman.py b/Plugins/UEWingman/ue-wingman.py index c6088dc0..31e75185 100755 --- a/Plugins/UEWingman/ue-wingman.py +++ b/Plugins/UEWingman/ue-wingman.py @@ -44,15 +44,6 @@ def main(): msg = {"command": args[0]} for arg in args[1:]: key, _, value = arg.partition("=") - if value.lower() == "true": - value = True - elif value.lower() == "false": - value = False - else: - try: - value = int(value) - except ValueError: - pass msg[key] = value sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)