New infrastructure for property manipulation.

This commit is contained in:
2026-04-02 02:27:14 -04:00
parent 7865818e69
commit 2e2bb89de0
17 changed files with 608 additions and 365 deletions

Binary file not shown.

View File

@@ -1,96 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingTypes.h"
#include "WingPackageMaker.h"
#include "Engine/Blueprint.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Blueprint_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_Create : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new Blueprint"))
FString AssetPath;
UPROPERTY(meta=(Description="Parent class, expressed as a type"))
FString ParentClass;
UPROPERTY(meta=(Optional, Description="Normal, Interface, FunctionLibrary, or MacroLibrary"))
TEnumAsByte<EBlueprintType> BlueprintType = BPTYPE_Normal;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Create a new Blueprint asset with a specified parent class and type."));
}
virtual void Handle() override
{
WingPackageMaker Maker(AssetPath);
if (!Maker.Ok()) return;
// Resolve parent class based on blueprint type
UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = true;
Req.AllowContainer = false;
UClass* ParentClassObj = nullptr;
switch (BlueprintType)
{
case BPTYPE_Normal:
ParentClassObj = UWingTypes::TextToOneObjectType(ParentClass, Req);
if (!ParentClassObj) return;
break;
case BPTYPE_MacroLibrary:
ParentClassObj = UWingTypes::TextToOneObjectType(ParentClass, Req);
if (!ParentClassObj) return;
break;
case BPTYPE_Interface:
ParentClassObj = UInterface::StaticClass();
break;
case BPTYPE_FunctionLibrary:
ParentClassObj = UBlueprintFunctionLibrary::StaticClass();
break;
default:
UWingServer::Print(TEXT("ERROR: BlueprintType must be Normal, Interface, FunctionLibrary, or MacroLibrary\n"));
return;
}
// Create the package and Blueprint
if (!Maker.Make()) return;
UBlueprint* NewBP = FKismetEditorUtilities::CreateBlueprint(
ParentClassObj,
Maker.Package(),
Maker.GetFName(),
BlueprintType,
UBlueprint::StaticClass(),
UBlueprintGeneratedClass::StaticClass()
);
if (!NewBP)
{
UWingServer::Print(TEXT("ERROR: FKismetEditorUtilities::CreateBlueprint returned null\n"));
return;
}
// Compile
FKismetEditorUtilities::CompileBlueprint(NewBP);
// Report result
UWingServer::Printf(TEXT("Created: %s\n"), *WingUtils::FormatName(NewBP));
}
};

View File

@@ -0,0 +1,105 @@
#pragma once
#include "CoreMinimal.h"
#include "Factories/Factory.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFactories.h"
#include "WingTypes.h"
#include "Factories/BlueprintFactory.h"
#include "Factories/BlueprintMacroFactory.h"
#include "Factories/AnimBlueprintFactory.h"
#include "WidgetBlueprintFactory.h"
#include "EditorUtilityBlueprintFactory.h"
#include "EditorUtilityWidgetBlueprintFactory.h"
#include "Create_Blueprint.generated.h"
UCLASS()
class UWing_Create_Blueprint : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new asset (e.g. '/Game/MyFolder/MyAsset')"))
FString Path;
UPROPERTY(meta=(Optional, Description="The parent class for the new blueprint"))
FString ParentClass;
static UClass *BlueprintInterfaceFactoryStaticClass()
{
return FindObject<UClass>(nullptr, TEXT("/Script/UnrealEd.BlueprintInterfaceFactory"));
}
virtual void Register() override
{
UWingServer::AddHandler(this, TEXT("Create_Blueprint"),
UBlueprintFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create a normal blueprint"));
UWingServer::AddHandler(this, TEXT("Create_BlueprintMacroLibrary"),
UBlueprintMacroFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create a blueprint macro library"));
UWingServer::AddHandler(this, TEXT("Create_WidgetBlueprint"),
UWidgetBlueprintFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create a widget blueprint"));
UWingServer::AddHandler(this, TEXT("Create_EditorUtilityBlueprint"),
UEditorUtilityBlueprintFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create an editor utility blueprint"));
UWingServer::AddHandler(this, TEXT("Create_EditorUtilityWidgetBlueprint"),
UEditorUtilityWidgetBlueprintFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create an editor utility widget blueprint"));
if (BlueprintInterfaceFactoryStaticClass() != nullptr)
UWingServer::AddHandler(this, TEXT("Create_BlueprintInterface"),
BlueprintInterfaceFactoryStaticClass(), EWingHandlerKind::Create,
TEXT("Create a blueprint interface"));
}
virtual void Handle() override
{
if (!WingFactories::CheckNewAssetPath(Path)) return;
// Resolve parent class, if specified.
UClass *ParentClassObj = nullptr;
if (!ParentClass.IsEmpty())
{
UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = true;
Req.AllowContainer = false;
ParentClassObj = UWingTypes::TextToOneObjectType(ParentClass, Req);
if (!ParentClassObj) return;
}
// Make the factory.
UClass *FactoryClass = Cast<UClass>(ConfigurationObject);
UFactory *Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass);
// Get the 'ParentClass' property.
FProperty *Prop = FactoryClass->FindPropertyByName(FName(TEXT("ParentClass")));
FClassProperty *CProp = CastField<FClassProperty>(Prop);
// Check that things are on track.
if ((Factory == nullptr) || (CProp == nullptr))
{
UWingServer::Printf(TEXT("In Create_Blueprint, factory creation is buggy\n"));
return;
}
// Store the parentclass, if specified.
if (ParentClassObj)
{
if (!ParentClassObj->IsChildOf(CProp->MetaClass))
{
UWingServer::Printf(TEXT("ParentClass must be child of %s.\n"),
*WingUtils::FormatName(CProp->MetaClass));
return;
}
CProp->SetObjectPropertyValue_InContainer(Factory, ParentClassObj);
}
// Create the asset.
UObject *Blueprint = WingFactories::CreateAsset(Path, Factory);
if (Blueprint == nullptr) return;
UWingServer::Printf(TEXT("Created.\n"));
}
};

View File

@@ -6,6 +6,7 @@
#include "WingHandler.h" #include "WingHandler.h"
#include "WingFactories.h" #include "WingFactories.h"
#include "WingProperty.h" #include "WingProperty.h"
#include "Factories/BlueprintFunctionLibraryFactory.h"
#include "Create_UsingFactory.generated.h" #include "Create_UsingFactory.generated.h"
UCLASS() UCLASS()
@@ -15,7 +16,7 @@ class UWing_Create_UsingFactory : public UWingHandler
public: public:
UPROPERTY(meta=(Description="Full asset path for the new asset (e.g. '/Game/MyFolder/MyAsset')")) UPROPERTY(meta=(Description="Full asset path for the new asset (e.g. '/Game/MyFolder/MyAsset')"))
FString AssetPath; FString Path;
virtual void Register() override virtual void Register() override
{ {
@@ -32,17 +33,22 @@ public:
TArray<FName> ConfigProps = FWingProperty::GetNames(Class, CPF_Edit); TArray<FName> ConfigProps = FWingProperty::GetNames(Class, CPF_Edit);
if (ConfigProps.Num() > 0) continue; if (ConfigProps.Num() > 0) continue;
FString FactoryName = UWingFactories::DeriveFactoryName(Class); FString FactoryName = WingFactories::DeriveFactoryName(Class);
FString CommandName = FString::Printf(TEXT("Create_%s"), *FactoryName); FString CommandName = FString::Printf(TEXT("Create_%s"), *FactoryName);
FString Doc = FString::Printf(TEXT("Create a new %s asset."), *FactoryName); FString Doc = FString::Printf(TEXT("Create a new %s asset."), *FactoryName);
UWingServer::AddHandler(this, CommandName, Doc, Class, EWingHandlerKind::Create); UWingServer::AddHandler(this, CommandName, Class, EWingHandlerKind::Create, Doc);
} }
UWingServer::AddHandler(this, TEXT("Create_BlueprintFunctionLibrary"),
UBlueprintFunctionLibraryFactory::StaticClass(), EWingHandlerKind::Create,
TEXT("Create a blueprint function library"));
} }
virtual void Handle() override virtual void Handle() override
{ {
UClass* FactoryClass = Cast<UClass>(ConfigurationObject); UClass* FactoryClass = Cast<UClass>(ConfigurationObject);
UFactory* Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass); UFactory* Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass);
UWingFactories::CreateAsset(AssetPath, Factory); WingFactories::CreateAsset(Path, Factory);
UWingServer::Printf(TEXT("Created.\n"));
} }
}; };

View File

@@ -1,43 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "Materials/Material.h"
#include "MaterialDomain.h"
#include "Factories/MaterialFactoryNew.h"
#include "WingPackageMaker.h"
#include "Material_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Material_Create : public UWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new material"))
FString Material;
virtual void Register() override
{
UWingServer::AddHandler(this,
TEXT("Create a new UMaterial asset"));
}
virtual void Handle() override
{
WingPackageMaker Maker(Material);
if (!Maker.Ok()) return;
// Create via IAssetTools + factory.
UMaterial* MaterialObj = Maker.CreateAsset<UMaterial, UMaterialFactoryNew>();
if (!MaterialObj) return;
UWingServer::Printf(TEXT("Created %s\n"), *MaterialObj->GetPathName());
}
};

View File

@@ -0,0 +1,78 @@
#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, CPF_Edit);
// 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

@@ -3,6 +3,7 @@
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "WingServer.h" #include "WingServer.h"
#include "WingHandler.h" #include "WingHandler.h"
#include "WingProperty.h"
#include "WingFactories.h" #include "WingFactories.h"
#include "SysInfo_Factories.generated.h" #include "SysInfo_Factories.generated.h"
@@ -24,25 +25,33 @@ public:
"is developing this plugin, they help him to understand. " "is developing this plugin, they help him to understand. "
"unreal's internals better.")); "unreal's internals better."));
} }
virtual void Handle() override virtual void Handle() override
{ {
const TArray<UWingFactories::Info>& All = UWingFactories::AllFactories(); TArray<UClass*> FactoryClasses;
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
for (int nparam = 0; nparam < 10; nparam++) TArray<FString> Results;
{
for (const UWingFactories::Info& Entry : All)
{
if (Entry.Config.Num() != nparam) continue;
if (!Entry.CanCreateNew()) continue;
UWingServer::Printf(TEXT("%s"), *Entry.Name);
for (const FName& Prop : Entry.Config) for (UClass *Factory : FactoryClasses)
{ {
UWingServer::Printf(TEXT(" PARAM: %s"), *Prop.ToString()); if (Factory->HasAnyClassFlags(CLASS_Abstract)) continue;
UFactory *CDO = Factory->GetDefaultObject<UFactory>();
if (!CDO->CanCreateNew()) continue;
if (!CDO->ShouldShowInNewMenu()) continue;
TArray<FName> Params = FWingProperty::GetNames(Factory, CPF_Edit);
TStringBuilder<512> Line;
Line.Appendf(TEXT("%2d %s "), Params.Num(), *Factory->GetName());
for (const FName& Prop : Params)
{
Line.Appendf(TEXT(" %s"), *Prop.ToString());
} }
UWingServer::Printf(TEXT("\n")); Results.Add(Line.ToString());
} }
UWingServer::Printf(TEXT("\n")); Results.Sort();
for (const FString &Line : Results)
{
UWingServer::Printf(TEXT("%s\n"), *Line);
} }
} }
}; };

View File

@@ -1,69 +1,11 @@
#include "WingFactories.h" #include "WingFactories.h"
#include "WingServer.h" #include "WingServer.h"
#include "Editor.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "WingProperty.h"
#include "PackageTools.h" #include "PackageTools.h"
#include "Kismet2/KismetEditorUtilities.h" #include "AssetRegistry/AssetRegistryModule.h"
#include "Kismet2/EnumEditorUtils.h" #include "Kismet2/EnumEditorUtils.h"
#include "Factories/BlueprintFactory.h"
#include "Factories/EnumFactory.h"
#include "Algo/BinarySearch.h"
void UWingFactories::Initialize(FSubsystemCollectionBase& Collection) bool WingFactories::CheckNewAssetPath(const FString& Path)
{
Super::Initialize(Collection);
PopulateRegistry();
}
const TArray<UWingFactories::Info>& UWingFactories::AllFactories()
{
return GEditor->GetEditorSubsystem<UWingFactories>()->Registry;
}
bool UWingFactories::Info::CanCreateNew() const
{
UFactory *CDO = FactoryClass->GetDefaultObject<UFactory>();
return CDO->CanCreateNew() && CDO->ShouldShowInNewMenu();
}
void UWingFactories::PopulateRegistry()
{
TArray<UClass*> FactoryClasses;
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
// Populate it with initial data.
for (UClass* Class : FactoryClasses)
{
if (Class->HasAnyClassFlags(CLASS_Abstract)) continue;
Info Entry;
Entry.Name = DeriveFactoryName(Class);
Entry.FactoryClass = Class;
Entry.Config = FWingProperty::GetNames(Class, CPF_Edit);
Registry.Add(MoveTemp(Entry));
}
// Sort the registry.
Registry.Sort([](const Info& A, const Info& B) { return A.Name < B.Name; });
// Blacklist certain bad factories.
DisableFactory(TEXT("PhysicsAsset")); // PhysicsAsset factory pops a modal dialog
}
void UWingFactories::DisableFactory(const TCHAR* Name)
{
Info* Entry = Find(Name);
if (!Entry)
{
UE_LOG(LogTemp, Fatal, TEXT("UWingFactories::DisableFactory: factory '%s' not found"), Name);
return;
}
Entry->Disabled = true;
}
bool UWingFactories::CheckNewAssetPath(const FString& Path)
{ {
if (UPackageTools::SanitizePackageName(Path) != Path) if (UPackageTools::SanitizePackageName(Path) != Path)
{ {
@@ -88,34 +30,36 @@ bool UWingFactories::CheckNewAssetPath(const FString& Path)
return true; return true;
} }
UPackage *UWingFactories::CreateNewPackage(const FString &Path) bool WingFactories::IsBlacklisted(UClass* FactoryClass)
{ {
UPackage *Pkg = CreatePackage(*Path); FName Name = FactoryClass->GetFName();
if (!Pkg) if (Name == TEXT("PhysicsAssetFactory")) return true; // Pops a modal dialog
if (Name == TEXT("EnumFactory")) return true; // Pops a modal dialog
return false;
}
UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory)
{
// Check the blacklist.
if (IsBlacklisted(Factory->GetClass()))
{
UWingServer::Printf(TEXT("ERROR: Factory '%s' is blacklisted\n"), *Factory->GetClass()->GetName());
return nullptr;
}
// Validate the path, and that there's not already something there.
if (!CheckNewAssetPath(Path)) return nullptr;
FName Name = FName(FPackageName::GetShortName(Path));
// Create the package.
UPackage *Package = CreatePackage(*Path);
if (!Package)
{ {
UWingServer::Printf(TEXT("ERROR: Failed to create package at '%s'\n"), *Path); UWingServer::Printf(TEXT("ERROR: Failed to create package at '%s'\n"), *Path);
return nullptr; return nullptr;
} }
Pkg->ClearFlags(RF_Transient);
Pkg->SetIsExternallyReferenceable(true);
Pkg->MarkPackageDirty();
return Pkg;
}
UObject* UWingFactories::CreateAsset(const FString& Path, UFactory* Factory)
{
// Validate the path, and that there's not already something there.
if (!CheckNewAssetPath(Path)) return nullptr;
FName Name = FName(FPackageName::GetShortName(Path));
// Pre-check: block factories that would cause problems.
// In particular, this blocks those that would pop a dialog.
if (!PreCheck(Factory, Name, Path)) return nullptr;
// Create the asset. // Create the asset.
UPackage *Package = CreateNewPackage(Path);
UObject* NewAsset = Factory->FactoryCreateNew( UObject* NewAsset = Factory->FactoryCreateNew(
Factory->GetSupportedClass(), Factory->GetSupportedClass(),
Package, Package,
@@ -124,105 +68,50 @@ UObject* UWingFactories::CreateAsset(const FString& Path, UFactory* Factory)
nullptr, nullptr,
GWarn GWarn
); );
if (!NewAsset)
// If the asset was created, turn the package into a
// permanent package and encourge the editor to save it
// to disk. Otherwise, mark the package for deletion.
if (NewAsset)
{ {
UWingServer::Printf(TEXT("ERROR: Factory '%s' returned null\n"), *Factory->GetClass()->GetName()); Package->ClearFlags(RF_Transient);
Package->SetIsExternallyReferenceable(true);
Package->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(NewAsset);
UWingServer::AddTouchedObject(NewAsset);
}
else
{
UWingServer::Printf(TEXT("ERROR: Factory '%s' failed to create an object\n"), *Factory->GetClass()->GetName());
Package->ClearDirtyFlag();
Package->MarkAsGarbage();
return nullptr; return nullptr;
} }
UWingServer::Printf(TEXT("Created: %s\n"), *WingUtils::ExternalizeID(Name));
return NewAsset; return NewAsset;
} }
UObject* UWingFactories::CreateAsset(const FString& Path, const FString& FactoryName, FWingJsonObject& Config) FString WingFactories::DeriveFactoryName(UClass* FactoryClass)
{
UWingFactories* Self = GEditor->GetEditorSubsystem<UWingFactories>();
// Look up the factory info.
const Info* FactoryInfo = Self->Find(FactoryName);
if (!FactoryInfo)
{
UWingServer::Printf(TEXT("ERROR: Unknown factory '%s'\n"), *FactoryName);
return nullptr;
}
// Make sure this is a creation factory, as opposed to an import factory.
if (!FactoryInfo->CanCreateNew())
{
UWingServer::Printf(TEXT("ERROR: Factory '%s' cannot create objects from scratch\n"), *FactoryName);
return nullptr;
}
// Create the factory instance.
UFactory* Factory = NewObject<UFactory>(GetTransientPackage(), FactoryInfo->FactoryClass);
// Get the editable properties
TArray<FWingProperty> Props =
FWingProperty::GetNamed(Factory->GetClass(), Factory, FactoryInfo->Config);
// if there is no config table, make a blank config table.
TSharedPtr<FJsonObject> ConfigJson = Config.Json;
if (ConfigJson == nullptr) ConfigJson = MakeShared<FJsonObject>();
// Populate the configuration properties from the json.
if (!FWingProperty::PopulateFromJson(Props, ConfigJson.Get(), false))
return nullptr;
return CreateAsset(Path, Factory);
}
bool UWingFactories::PreCheck(UFactory* Factory, FName Name, const FString &Path)
{
// Blueprint factories: FactoryCreateNew pops FMessageDialog if ParentClass is invalid.
if (UBlueprintFactory* BPFactory = Cast<UBlueprintFactory>(Factory))
{
if (!BPFactory->ParentClass)
{
UWingServer::Print(TEXT("ERROR: ParentClass must be set\n"));
return false;
}
if (!FKismetEditorUtilities::CanCreateBlueprintOfClass(BPFactory->ParentClass))
{
UWingServer::Printf(TEXT("ERROR: Cannot create a blueprint based on class '%s'\n"), *BPFactory->ParentClass->GetName());
return false;
}
}
// Enum factory: FactoryCreateNew pops FMessageDialog if the name already exists.
if (UEnumFactory* EFactory = Cast<UEnumFactory>(Factory))
{
if(!FEnumEditorUtils::IsNameAvailebleForUserDefinedEnum(Name))
{
UWingServer::Printf(TEXT("Enum name is already taken: %s"),
*WingUtils::ExternalizeID(Name));
}
}
return true;
}
UWingFactories::Info* UWingFactories::Find(const FString& Name)
{
int32 Index = Algo::LowerBound(Registry, Name, [](Info& Entry, const FString& N) {
return Entry.Name < N;
});
if (Index < Registry.Num() && Registry[Index].Name == Name)
{
return &Registry[Index];
}
return nullptr;
}
FString UWingFactories::DeriveFactoryName(UClass* FactoryClass)
{ {
FString Name = FactoryClass->GetName(); FString Name = FactoryClass->GetName();
if (Name.EndsWith(TEXT("FactoryNew"))) int32 Index = Name.Find(TEXT("Factory"));
if (Index != INDEX_NONE)
{ {
Name.LeftChopInline(10); Name.LeftInline(Index);
} }
else if (Name.EndsWith(TEXT("Factory"))) if (Name.EndsWith(TEXT("_")))
{ {
Name.LeftChopInline(7); Name.LeftChopInline(1);
} }
return Name; return Name;
} }
UObject* UEnumFactoryWing::FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn)
{
if (!FEnumEditorUtils::IsNameAvailebleForUserDefinedEnum(Name))
{
UWingServer::Printf(TEXT("ERROR: Enum name is already taken: %s\n"), *Name.ToString());
return nullptr;
}
return FEnumEditorUtils::CreateUserDefinedEnum(InParent, Name, Flags);
}

View File

@@ -0,0 +1,234 @@
#include "WingPropHandle.h"
#include "WingServer.h"
#include "WingActorComponent.h"
#include "PropertyEditorModule.h"
#include "IDetailTreeNode.h"
#include "Engine/Blueprint.h"
#include "Components/Widget.h"
#include "Components/PanelSlot.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "Materials/MaterialExpression.h"
#include "DetailTreeNode.h"
#include "PropertyNode.h"
#include "ObjectPropertyNode.h"
#include "PropertyNode.h"
#include "ObjectPropertyNode.h"
#include "ItemPropertyNode.h"
#include "CategoryPropertyNode.h"
/////////////////////////////////////////////////////////////////////////////
//
// IsSubObject
//
/////////////////////////////////////////////////////////////////////////////
bool WingPropHandle::IsSubObject(const TSharedRef<IDetailTreeNode>& Node)
{
FDetailTreeNode& DetailNode = static_cast<FDetailTreeNode&>(*Node);
TSharedPtr<FPropertyNode> PropNode = DetailNode.GetPropertyNode();
if (!PropNode.IsValid()) return false;
FPropertyNode* Current = PropNode.Get();
while (true)
{
if (Current == nullptr) return false;
FPropertyNode *Parent = Current->GetParentNode();
if (Current->AsObjectNode()) return (Parent != nullptr);
Current = Parent;
}
}
/////////////////////////////////////////////////////////////////////////////
//
// Get Generator
//
/////////////////////////////////////////////////////////////////////////////
TSharedRef<IPropertyRowGenerator> WingPropHandle::CreateGenerator()
{
FPropertyEditorModule& Module = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
FPropertyRowGeneratorArgs Args;
Args.bShouldShowHiddenProperties = false;
return Module.CreatePropertyRowGenerator(Args);
}
TSharedRef<IPropertyRowGenerator> WingPropHandle::GetGeneratorForObject(UObject* Obj)
{
for (auto& Pair : Generators)
{
if (Pair.Key == Obj) return Pair.Value.ToSharedRef();
}
TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
Gen->SetObjects({Obj});
Generators.Add({Obj, Gen});
return Gen;
}
TSharedRef<IPropertyRowGenerator> WingPropHandle::GetGeneratorForStruct(const UStruct* ScriptStruct, uint8* Data)
{
for (auto& Pair : Generators)
{
if (Pair.Key == Data) return Pair.Value.ToSharedRef();
}
TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
TSharedPtr<FStructOnScope> Wrapper = MakeShared<FStructOnScope>(ScriptStruct, Data);
Gen->SetStructure(Wrapper);
Generators.Add({Data, Gen});
return Gen;
}
/////////////////////////////////////////////////////////////////////////////
//
// All Tree Nodes
//
/////////////////////////////////////////////////////////////////////////////
void WingPropHandle::AllTreeNodesRecursive(const TSharedRef<IDetailTreeNode>& Node, FlatTree& Out)
{
if (IsSubObject(Node)) return;
if (Node->GetNodeType() == EDetailNodeType::Category)
{
TArray<TSharedRef<IDetailTreeNode>> Children;
Node->GetChildren(Children);
for (const TSharedRef<IDetailTreeNode>& Child : Children)
{
AllTreeNodesRecursive(Child, Out);
}
return;
}
Out.Add(&*Node);
}
WingPropHandle::FlatTree WingPropHandle::AllTreeNodes(TSharedRef<IPropertyRowGenerator> Generator)
{
FlatTree Result;
for (const TSharedRef<IDetailTreeNode>& Root : Generator->GetRootTreeNodes())
{
AllTreeNodesRecursive(Root, Result);
}
return Result;
}
/////////////////////////////////////////////////////////////////////////////
//
// AllProperties
//
/////////////////////////////////////////////////////////////////////////////
WingPropHandle::Handles WingPropHandle::AllProperties(TSharedRef<IPropertyRowGenerator> Generator, EPropertyFlags Filter)
{
Handles Result;
for (IDetailTreeNode* Node : AllTreeNodes(Generator))
{
TSharedPtr<IPropertyHandle> Handle = Node->CreatePropertyHandle();
if (Handle.IsValid() && Handle->GetProperty())
{
if (Filter == CPF_None || Handle->GetProperty()->HasAllPropertyFlags(Filter))
{
Result.Add(Handle);
}
}
}
return Result;
}
WingPropHandle::Handles WingPropHandle::AllProperties(UObject* Obj, EPropertyFlags Filter)
{
if (!Obj) return {};
return AllProperties(GetGeneratorForObject(Obj), Filter);
}
WingPropHandle::Handles WingPropHandle::AllProperties(const UStruct* ScriptStruct, uint8* Data, EPropertyFlags Filter)
{
if (!ScriptStruct || !Data) return {};
return AllProperties(GetGeneratorForStruct(ScriptStruct, Data), Filter);
}
/////////////////////////////////////////////////////////////////////////////
//
// Named Property
//
/////////////////////////////////////////////////////////////////////////////
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(TSharedRef<IPropertyRowGenerator> Generator, FName Name)
{
for (IDetailTreeNode* Node : AllTreeNodes(Generator))
{
if (Node->GetNodeName() == Name)
return Node->CreatePropertyHandle();
}
return nullptr;
}
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(UObject* Obj, FName Name)
{
if (!Obj) return nullptr;
return NamedProperty(GetGeneratorForObject(Obj), Name);
}
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name)
{
if (!ScriptStruct || !Data) return nullptr;
return NamedProperty(GetGeneratorForStruct(ScriptStruct, Data), Name);
}
/////////////////////////////////////////////////////////////////////////////
//
// GetDetails
//
/////////////////////////////////////////////////////////////////////////////
WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable, EPropertyFlags Filter)
{
if (!Obj) return {};
// Blueprints: redirect to the generated class CDO.
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
{
if (!BP->GeneratedClass)
{
UWingServer::Printf(TEXT("ERROR: Blueprint '%s' has no GeneratedClass\n"), *Obj->GetName());
return {};
}
Obj = BP->GeneratedClass->GetDefaultObject();
}
// Component references: redirect to the template.
if (UWingComponentReference* Ref = Cast<UWingComponentReference>(Obj))
{
Obj = Mutable ? Ref->GetMutableTemplate() : Ref->GetImmutableTemplate();
if (!Obj)
{
UWingServer::Printf(TEXT("ERROR: Component '%s' has no template\n"), *Ref->VariableName.ToString());
return {};
}
}
Handles Result = AllProperties(Obj, Filter);
// Material graph nodes: also collect expression properties.
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Obj))
{
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
{
Result.Append(AllProperties(Expr, Filter));
}
}
// Widgets: hide the Slot property, add slot properties.
if (UWidget* Widget = Cast<UWidget>(Obj))
{
Result.RemoveAll([](const TSharedPtr<IPropertyHandle>& H)
{
return H->GetProperty()->GetFName() == TEXT("Slot");
});
if (UPanelSlot* Slot = Widget->Slot)
{
Result.Append(AllProperties(Slot, Filter));
}
}
return Result;
}

View File

@@ -454,10 +454,10 @@ void UWingServer::ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnec
void UWingServer::AddHandler(UObject* Obj, const FString& Documentation) void UWingServer::AddHandler(UObject* Obj, const FString& Documentation)
{ {
AddHandler(Obj, WingUtils::GetHandlerName(Obj->GetClass()), Documentation, nullptr, EWingHandlerKind::Normal); AddHandler(Obj, WingUtils::GetHandlerName(Obj->GetClass()), nullptr, EWingHandlerKind::Normal, Documentation);
} }
void UWingServer::AddHandler(UObject* Obj, const FString& Name, const FString& Documentation, UObject* Config, EWingHandlerKind Kind) void UWingServer::AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, const FString& Documentation)
{ {
FWingHandlerConfig H; FWingHandlerConfig H;
H.Name = Name; H.Name = Name;

View File

@@ -5,6 +5,7 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingHandler.h" #include "WingHandler.h"
#include "WingTokenizer.h" #include "WingTokenizer.h"
#include "PropertyHandle.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "Engine/MemberReference.h" #include "Engine/MemberReference.h"
#include "Engine/World.h" #include "Engine/World.h"
@@ -56,6 +57,7 @@
// ============================================================ // ============================================================
FName WingUtils::GetFName(const FWingProperty &Prop) { return Prop.Prop->GetFName(); } FName WingUtils::GetFName(const FWingProperty &Prop) { return Prop.Prop->GetFName(); }
FName WingUtils::GetFName(const TSharedPtr<IPropertyHandle> &H) { return H->GetProperty()->GetFName(); }
FString WingUtils::ExternalizeID(FName Name) FString WingUtils::ExternalizeID(FName Name)
{ {

View File

@@ -1,47 +1,41 @@
#pragma once #pragma once
#include "CoreMinimal.h" #include "CoreMinimal.h"
#include "EditorSubsystem.h"
#include "Factories/Factory.h" #include "Factories/Factory.h"
#include "WingHandler.h" #include "Factories/EnumFactory.h"
#include "WingFactories.generated.h" #include "WingFactories.generated.h"
UCLASS()
class UWingFactories : public UEditorSubsystem
{
GENERATED_BODY()
class WingFactories
{
public: public:
struct Info // Create an asset on disk, using a factory instance.
{ // Returns the main object. If there are problems,
FString Name; // prints error messages and returns nullptr.
UClass* FactoryClass = nullptr;
TArray<FName> Config;
bool Disabled = false;
bool CanCreateNew() const;
};
virtual void Initialize(FSubsystemCollectionBase& Collection) override;
virtual void Deinitialize() override {}
static const TArray<Info>& AllFactories();
// Create an asset on disk, using a factory instance. Returns the main object.
// If there are problems, prints error messages and returns nullptr.
static UObject *CreateAsset(const FString &Path, UFactory *Factory); static UObject *CreateAsset(const FString &Path, UFactory *Factory);
// Create an asset on disk, looking up the factory by name and configuring it. // Some factories are blacklisted, mainly because they
static UObject *CreateAsset(const FString &Path, const FString &FactoryName, FWingJsonObject &Config); // pop up dialog boxes. In those cases, we deal with it
// primarily by adding those factories to the blacklist,
// and then implementing replacement factories.
static bool IsBlacklisted(UClass* FactoryClass);
// This takes a factory name and turns it into a string
// that we can present to the user. Mainly, it removes
// the word 'Factory', and anything that comes after.
static FString DeriveFactoryName(UClass* FactoryClass); static FString DeriveFactoryName(UClass* FactoryClass);
private: // Verifies that the asset path is a valid path, and also
TArray<Info> Registry; // that there's not something already there at that path.
Info* Find(const FString& Name);
static UPackage *CreateNewPackage(const FString &Path);
static bool CheckNewAssetPath(const FString &Path); static bool CheckNewAssetPath(const FString &Path);
void PopulateRegistry(); };
void DisableFactory(const TCHAR* Name);
static bool PreCheck(UFactory *Factory, FName Name, const FString &Path); // The original UEnumFactory may pop a dialog. We made a better one.
UCLASS()
class UEnumFactoryWing : public UEnumFactory
{
GENERATED_BODY()
public:
virtual UObject* FactoryCreateNew(UClass* Class, UObject* InParent, FName Name, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override;
}; };

View File

@@ -5,7 +5,6 @@
struct FToolMenuEntry; struct FToolMenuEntry;
struct FToolUIActionChoice; struct FToolUIActionChoice;
class FBlueprintEditor; class FBlueprintEditor;
// Utility class that uses the C++ template explicit-instantiation // Utility class that uses the C++ template explicit-instantiation
// loophole to access private members of engine classes. // loophole to access private members of engine classes.
// See WingHacks.cpp for details on the technique. // See WingHacks.cpp for details on the technique.

View File

@@ -0,0 +1,61 @@
#pragma once
#include "CoreMinimal.h"
#include "IPropertyRowGenerator.h"
#include "PropertyHandle.h"
// WingPropHandle: A wrapper around IPropertyRowGenerator that
// provides easy access to IPropertyHandle objects.
//
// Usage:
// WingPropHandle Props;
// TArray<TSharedPtr<IPropertyHandle>> Handles = Props.AddObject(MyObject);
//
// The generators and all handles are valid for the lifetime of
// this object.
class WingPropHandle
{
public:
using Handles = TArray<TSharedPtr<IPropertyHandle>>;
using FlatTree = TArray<IDetailTreeNode *, TInlineAllocator<500>>;
WingPropHandle() {}
// 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);
// 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);
// 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<IPropertyHandle> NamedProperty(UObject* Obj, FName Name);
// Get a single named property from a struct.
TSharedPtr<IPropertyHandle> NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name);
private:
TArray<TPair<void*, TSharedPtr<IPropertyRowGenerator>>> Generators;
// Check whether a detail tree node's property comes from a sub-object.
static bool IsSubObject(const TSharedRef<IDetailTreeNode>& Node);
static TSharedRef<IPropertyRowGenerator> CreateGenerator();
TSharedRef<IPropertyRowGenerator> GetGeneratorForObject(UObject* Obj);
TSharedRef<IPropertyRowGenerator> GetGeneratorForStruct(const UStruct* ScriptStruct, uint8* Data);
static void AllTreeNodesRecursive(const TSharedRef<IDetailTreeNode>& Node, FlatTree& Out);
static FlatTree AllTreeNodes(TSharedRef<IPropertyRowGenerator> Generator);
Handles AllProperties(TSharedRef<IPropertyRowGenerator> Generator, EPropertyFlags Filter);
static TSharedPtr<IPropertyHandle> NamedProperty(TSharedRef<IPropertyRowGenerator> Generator, FName Name);
};

View File

@@ -75,7 +75,7 @@ public:
// ----- Tool dispatch ----- // ----- Tool dispatch -----
static void AddHandler(UObject* Obj, const FString& Documentation); static void AddHandler(UObject* Obj, const FString& Documentation);
static void AddHandler(UObject* Obj, const FString& Name, const FString& Documentation, UObject* Config, EWingHandlerKind Kind); static void AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, const FString& Documentation);
static const TArray<FWingHandlerConfig>& AllHandlers() { return GWingServer->WingHandlerRegistry; } static const TArray<FWingHandlerConfig>& AllHandlers() { return GWingServer->WingHandlerRegistry; }
private: private:

View File

@@ -24,6 +24,7 @@ class UScriptStruct;
class UEnum; class UEnum;
struct FBPInterfaceDescription; struct FBPInterfaceDescription;
struct FWingProperty; struct FWingProperty;
class IPropertyHandle;
#include "Engine/World.h" #include "Engine/World.h"
#include "Materials/Material.h" #include "Materials/Material.h"
@@ -65,6 +66,7 @@ public:
static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; } static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; }
static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); } static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); }
static FName GetFName(const WingVariables::Var &Var) { return Var.Name; } static FName GetFName(const WingVariables::Var &Var) { return Var.Name; }
static FName GetFName(const TSharedPtr<IPropertyHandle> &H);
//////////////////////////////////////////////////////// ////////////////////////////////////////////////////////
// //

View File

@@ -6,6 +6,10 @@ public class UEWingman : ModuleRules
{ {
PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
// Needed for debug dump of FPropertyNode tree (private headers)
PrivateIncludePaths.Add(System.IO.Path.Combine(EngineDirectory, "Source/Editor/PropertyEditor/Private"));
PublicDependencyModuleNames.AddRange(new string[] PublicDependencyModuleNames.AddRange(new string[]
{ {
"Core", "Core",
@@ -34,7 +38,9 @@ public class UEWingman : ModuleRules
"SlateCore", "SlateCore",
"ToolMenus", "ToolMenus",
"UMG", "UMG",
"UMGEditor" "UMGEditor",
"Blutility",
"PropertyEditor"
}); });
} }
} }