More work on property

This commit is contained in:
2026-04-03 15:03:59 -04:00
parent e70bce0ede
commit c19091ef1f
12 changed files with 157 additions and 267 deletions

View File

@@ -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<UObject>();
if (!Target) return;
WingPropHandle Props;
WingPropHandle::Handles Handles = Props.GetDetails(Target, false);
// Group by category, preserving within-category order.
FString QueryLower = Query.ToLower();
TSortedMap<FString, TArray<TSharedPtr<IPropertyHandle>>> Categories;
for (const TSharedPtr<IPropertyHandle>& 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<IPropertyHandle>& H : Pair.Value)
WingPropHandle::Print(*H, Out);
}
}
};

View File

@@ -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()

View File

@@ -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<UObject>();
if (!Obj) return;
WingPropHandle Props;
WingPropHandle::Handles Handles = Props.GetDetails(Obj, true);
TSharedPtr<IPropertyHandle>* P = WingUtils::FindOneWithExternalID(Property, Handles, TEXT("Property"));
if (!P) return;
if (WingPropHandle::SetText(**P, Value))
UWingServer::Print(TEXT("OK\n"));
}
};

View File

@@ -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()

View File

@@ -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<UMaterialInterface>(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<UMaterialInstanceConstant, UMaterialInstanceConstantFactoryNew>();
if (!MI) return;
// Set parent.
MI->Parent = ParentMaterialObj;
UWingServer::Printf(TEXT("Created %s\n"), *MI->GetPathName());
if (UMaterialInstance* ParentMI = Cast<UMaterialInstance>(ParentMaterialObj))
UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMI));
else if (UMaterial* ParentMat = Cast<UMaterial>(ParentMaterialObj))
UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMat));
else
UWingServer::Printf(TEXT("Parent: %s\n"), *ParentMaterialObj->GetPathName());
}
};

View File

@@ -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<UObject>();
if (!Target) return;
WingPropHandle Props;
WingPropHandle::Handles Handles = Props.GetDetails(Target, false);
// Sort by category name for consistent grouping.
Handles.Sort([](const TSharedPtr<IPropertyHandle>& A, const TSharedPtr<IPropertyHandle>& B)
{
return A->GetProperty()->GetMetaData(TEXT("Category")) < B->GetProperty()->GetMetaData(TEXT("Category"));
});
FString QueryLower = Query.ToLower();
FString CurrentCategory;
for (const TSharedPtr<IPropertyHandle>& 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);
}
}
};

View File

@@ -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<UPackage>(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;
}

View File

@@ -283,6 +283,17 @@ FString WingPropHandle::GetText(IPropertyHandle& Handle)
return UWingTypes::TypeToText(*static_cast<FEdGraphPinType*>(Data));
}
// Class properties: use our human-readable type names.
if (CastField<FClassProperty>(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<FClassProperty>(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 TSharedPtr<FJsonValu
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(" "));
@@ -396,7 +427,7 @@ void WingPropHandle::Print(IPropertyHandle& Handle, FStringBuilderBase& Out)
Out.Appendf(TEXT(" %s %s %s = %s\n"),
bEditable ? TEXT("editable") : TEXT("readonly"),
*UWingTypes::TypeToText(Prop),
*WingUtils::FormatName(Prop),
*UWingTypes::TypeToText(Handle.GetProperty()),
*WingUtils::FormatName(Handle),
*Value);
}

View File

@@ -242,6 +242,16 @@ FString WingUtils::FormatName(const FProperty *Prop)
return ExternalizeID(Prop->GetFName());
}
FString WingUtils::FormatName(IPropertyHandle &Handle)
{
return ExternalizeID(Handle.GetProperty()->GetFName());
}
FString WingUtils::FormatName(const TSharedPtr<IPropertyHandle> &Handle)
{
return FormatName(*Handle);
}
FString WingUtils::FormatName(const FUserPinInfo &Pin)
{
return ExternalizeID(Pin.PinName);

View File

@@ -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<typename AssetClass, typename FactoryClass>
AssetClass* CreateAsset()
{
if (bError) return nullptr;
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
FactoryClass* Factory = NewObject<FactoryClass>();
FString PkgPath = FPackageName::GetLongPackagePath(FullPath);
FString AssetName = FPackageName::GetShortName(FullPath);
UObject* NewAsset = AssetTools.CreateAsset(AssetName, PkgPath, AssetClass::StaticClass(), Factory);
AssetClass* Result = Cast<AssetClass>(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);
};

View File

@@ -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<IPropertyHandle> &Handle);
static FString FormatName(const FUserPinInfo &Pin);
static FString FormatName(const FBPInterfaceDescription &IFace);
static FString FormatName(const UWingComponentReference *Ref);

View File

@@ -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)