Compare commits
10 Commits
ba63a40641
...
8951065670
| Author | SHA1 | Date | |
|---|---|---|---|
| 8951065670 | |||
| 7aac8f194a | |||
| 5206700067 | |||
| 9c1f474170 | |||
| c949a4db05 | |||
| bd138e2790 | |||
| fb5b774bfe | |||
| 47acf1aca4 | |||
| c006531bd4 | |||
| 92e41c857a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -51,3 +51,4 @@ GPF-output/**
|
||||
|
||||
__pycache__/
|
||||
.clangd-query/
|
||||
COMMIT.txt
|
||||
|
||||
Binary file not shown.
100
Docs/Unsupported-SingleClass-Factories.md
Normal file
100
Docs/Unsupported-SingleClass-Factories.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Unsupported Factories with 1 Class-Type Parameter
|
||||
|
||||
## Standard candidates (class picker, flag-based filtering)
|
||||
|
||||
All of these follow the same basic pattern: TSubclassOf<Base>, open a
|
||||
class picker dialog, filter on class flags.
|
||||
|
||||
- AudioSynesthesiaNRTFactory — AudioSynesthesiaNRTClass
|
||||
TSubclassOf<UAudioSynesthesiaNRT>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists
|
||||
|
||||
- AudioSynesthesiaNRTSettingsFactory — AudioSynesthesiaNRTSettingsClass
|
||||
TSubclassOf<UAudioSynesthesiaNRTSettings>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists
|
||||
|
||||
- AudioSynesthesiaSettingsFactory — AudioSynesthesiaSettingsClass
|
||||
TSubclassOf<UAudioSynesthesiaSettings>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists
|
||||
|
||||
- CurveFactory — CurveClass
|
||||
TSubclassOf<UCurveBase>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists
|
||||
Also checks CanCreateAssetOfClass()
|
||||
|
||||
- DataAssetFactory — DataAssetClass
|
||||
TSubclassOf<UDataAsset>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists, no hide-dropdown
|
||||
Also checks CanCreateAssetOfClass()
|
||||
|
||||
- InputAction_Factory — InputActionClass
|
||||
TSubclassOf<UInputAction>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists, no hide-dropdown
|
||||
Skips dialog if no subtypes exist
|
||||
|
||||
- InputMappingContext_Factory — InputMappingContextClass
|
||||
TSubclassOf<UInputMappingContext>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists, no hide-dropdown
|
||||
Skips dialog if no subtypes exist
|
||||
|
||||
- PhysicalMaterialFactoryNew — PhysicalMaterialClass
|
||||
TSubclassOf<UPhysicalMaterial>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists
|
||||
Also checks CanCreateAssetOfClass()
|
||||
|
||||
- PhysicalMaterialMaskFactory — PhysicalMaterialMaskClass
|
||||
TSubclassOf<UPhysicalMaterialMask>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists
|
||||
Also checks CanCreateAssetOfClass()
|
||||
|
||||
- SoundSourceEffectFactory — SoundEffectSourcepresetClass
|
||||
TSubclassOf<USoundEffectSourcePreset>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists
|
||||
|
||||
- SoundSubmixEffectFactory — SoundEffectSubmixPresetClass
|
||||
TSubclassOf<USoundEffectSubmixPreset>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists
|
||||
|
||||
## Slightly unusual candidates
|
||||
|
||||
- CameraAssetFactory — CameraDirectorClass
|
||||
TSubclassOf<UCameraDirector>
|
||||
Flags: no abstract, no deprecated (does NOT check newer-version-exists)
|
||||
Allows "None" option (parameter can be null)
|
||||
|
||||
- SlateWidgetStyleAssetFactory — StyleType
|
||||
TSubclassOf<USlateWidgetStyleContainerBase>
|
||||
Flags: no abstract, no deprecated, no newer-version-exists
|
||||
Explicitly excludes the base class itself
|
||||
|
||||
- TemplateSequenceFactoryNew — BoundActorClass
|
||||
TSubclassOf<UObject> but dialog restricts to actors only (bIsActorsOnly)
|
||||
Effective base class is AActor
|
||||
No explicit flag filtering beyond class viewer defaults
|
||||
|
||||
## Complex / special cases (probably not suitable for Create_ClassArg)
|
||||
|
||||
- ControlRigBlueprintFactory — ParentClass
|
||||
TSubclassOf<UControlRig>
|
||||
Complex custom filter: native-only, must have IsBlueprintBase="true"
|
||||
metadata, no blueprint parents. Also calls CanCreateBlueprintOfClass
|
||||
in FactoryCreateNew. Dialog gated behind a CVar.
|
||||
|
||||
- LiveLinkBlueprintVirtualSubjectFactory — ParentClass
|
||||
ParentClass is hardcoded. The actual configured parameter is "Role"
|
||||
(TSubclassOf<ULiveLinkRole>) with a custom modal dialog and a filter
|
||||
that checks IsAllowableBlueprintVariableType on the role's structs.
|
||||
Different parameter than what SysInfo_Factories reported.
|
||||
|
||||
## Not class parameters at all (remove from consideration)
|
||||
|
||||
- MetaSoundFactory — ReferencedMetaSoundObject
|
||||
TObjectPtr<UObject>, not TSubclassOf. Optional reference to an
|
||||
existing MetaSound object for preset initialization.
|
||||
|
||||
- MetaSoundSourceFactory — ReferencedMetaSoundObject
|
||||
Same as above.
|
||||
|
||||
- ObjectMixerBlueprintFilterFactory — ParentClass
|
||||
Hardcoded to UObjectMixerBlueprintObjectFilter in constructor.
|
||||
No config dialog. Nothing to configure.
|
||||
0
Docs/bugs-in-ue-wingman.md
Normal file
0
Docs/bugs-in-ue-wingman.md
Normal file
@@ -58,12 +58,12 @@ public:
|
||||
|
||||
// Resolve the component class by name
|
||||
UWingTypes::Requirements Req;
|
||||
Req.BlueprintType = false;
|
||||
Req.Blueprintable = false;
|
||||
Req.AllowContainer = false;
|
||||
Req.IsChildOf = UActorComponent::StaticClass();
|
||||
UClass* ComponentClass = UWingTypes::TextToOneObjectType(Class, Req, WingOut::Stdout);
|
||||
if (!ComponentClass) return;
|
||||
FEdGraphPinType PinType;
|
||||
if (!UWingTypes::TextToType(Class, PinType, Req, WingOut::Stdout)) return;
|
||||
UClass* ComponentClass = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
check(ComponentClass);
|
||||
if (!UWingComponentReference::CheckValidComponentClass(ComponentClass, WingOut::Stdout)) return;
|
||||
|
||||
// Find the specified parent component
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Asset to delete"))
|
||||
FString Asset;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="If true, skip reference check and force delete"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, skip reference check and force delete"))
|
||||
bool Force = false;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -21,13 +21,13 @@ class UWing_Asset_Search : public UWingHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Optional, Description="Substring to match against asset package paths"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Substring to match against asset package paths"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Asset class name to filter by, e.g. Blueprint, Material, StaticMesh"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Asset class name to filter by, e.g. Blueprint, Material, StaticMesh"))
|
||||
FString Type;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results (default 50)"))
|
||||
int32 Limit = 50;
|
||||
|
||||
virtual void Register() override
|
||||
@@ -52,11 +52,12 @@ public:
|
||||
if (!Type.IsEmpty())
|
||||
{
|
||||
UWingTypes::Requirements Req;
|
||||
Req.BlueprintType = false;
|
||||
Req.Blueprintable = false;
|
||||
Req.AllowContainer = false;
|
||||
UClass* TypeClass = UWingTypes::TextToOneObjectType(Type, Req, WingOut::Stdout);
|
||||
if (!TypeClass) return;
|
||||
Req.IsChildOf = UObject::StaticClass();
|
||||
FEdGraphPinType PinType;
|
||||
if (!UWingTypes::TextToType(Type, PinType, Req, WingOut::Stdout)) return;
|
||||
UClass* TypeClass = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
check(TypeClass);
|
||||
Filter.ClassPaths.Add(TypeClass->GetClassPathName());
|
||||
}
|
||||
|
||||
|
||||
@@ -32,10 +32,10 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Type of graph: function or macro"))
|
||||
FString GraphType;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Input variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variables, one per line"))
|
||||
FString InputVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Output variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variables, one per line"))
|
||||
FString OutputVariables;
|
||||
|
||||
virtual void Register() override
|
||||
@@ -75,8 +75,9 @@ public:
|
||||
// Check graph name uniqueness and legality
|
||||
FName InternalID = WingUtils::CheckProposedName(Graph, WingOut::Stdout);
|
||||
if (InternalID.IsNone()) return;
|
||||
if (!WingUtils::FindNoneWithInternalID(InternalID, WingUtils::AllGraphs(BP), TEXT("Graph"), WingOut::Stdout))
|
||||
return;
|
||||
TSet<FName> Names;
|
||||
if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("Graph"), WingOut::Stdout)) return;
|
||||
if (!WingUtils::FindNoDuplicateNames(Names, WingUtils::AllGraphs(BP), TEXT("Graph"), WingOut::Stdout)) return;
|
||||
|
||||
// Parse and validate variables before making changes
|
||||
WingVariables Vars;
|
||||
|
||||
@@ -40,11 +40,12 @@ public:
|
||||
|
||||
// Resolve the interface class
|
||||
UWingTypes::Requirements Req;
|
||||
Req.BlueprintType = false;
|
||||
Req.Blueprintable = false;
|
||||
Req.AllowContainer = false;
|
||||
UClass* InterfaceClass = UWingTypes::TextToOneInterfaceType(Interface, Req, WingOut::Stdout);
|
||||
if (!InterfaceClass) return;
|
||||
Req.IsChildOf = UInterface::StaticClass();
|
||||
FEdGraphPinType PinType;
|
||||
if (!UWingTypes::TextToType(Interface, PinType, Req, WingOut::Stdout)) return;
|
||||
UClass* InterfaceClass = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
check(InterfaceClass);
|
||||
|
||||
// Check for duplicates
|
||||
for (const FBPInterfaceDescription& IfaceDesc : BP->ImplementedInterfaces)
|
||||
|
||||
@@ -27,7 +27,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Interface name to remove"))
|
||||
FString Interface;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="If true, keep the function graphs as regular functions"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, keep the function graphs as regular functions"))
|
||||
bool PreserveFunctions = false;
|
||||
|
||||
virtual void Register() override
|
||||
@@ -44,11 +44,12 @@ public:
|
||||
|
||||
// Resolve the interface name to a UClass*
|
||||
UWingTypes::Requirements Req;
|
||||
Req.BlueprintType = false;
|
||||
Req.Blueprintable = false;
|
||||
Req.AllowContainer = false;
|
||||
UClass* FoundInterface = UWingTypes::TextToOneInterfaceType(Interface, Req, WingOut::Stdout);
|
||||
if (!FoundInterface) return;
|
||||
Req.IsChildOf = UInterface::StaticClass();
|
||||
FEdGraphPinType PinType;
|
||||
if (!UWingTypes::TextToType(Interface, PinType, Req, WingOut::Stdout)) return;
|
||||
UClass* FoundInterface = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
check(FoundInterface);
|
||||
|
||||
// Verify this blueprint actually implements it
|
||||
bool Found = false;
|
||||
|
||||
@@ -42,12 +42,14 @@ public:
|
||||
|
||||
// Find the new parent class by short type name
|
||||
UWingTypes::Requirements Req;
|
||||
Req.BlueprintType = false;
|
||||
Req.Blueprintable = true;
|
||||
Req.AllowContainer = false;
|
||||
UClass* NewParentClassObj = UWingTypes::TextToOneObjectType(Parent, Req, WingOut::Stdout);
|
||||
if (!NewParentClassObj) return;
|
||||
|
||||
Req.IsChildOf = UObject::StaticClass();
|
||||
FEdGraphPinType PinType;
|
||||
if (!UWingTypes::TextToType(Parent, PinType, Req, WingOut::Stdout)) return;
|
||||
UClass* NewParentClassObj = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
check(NewParentClassObj);
|
||||
|
||||
// Validate reparent
|
||||
if (!WingUtils::CanReparentBlueprint(BP->GeneratedClass, NewParentClassObj))
|
||||
{
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Factories/Factory.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingFactories.h"
|
||||
#include "WingPropHandle.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(EditAnywhere, 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, WingOut::Stdout)) 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, WingOut::Stdout);
|
||||
if (!ParentClassObj) return;
|
||||
}
|
||||
|
||||
// Make the factory.
|
||||
UClass *FactoryClass = Cast<UClass>(ConfigurationObject);
|
||||
UFactory *Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass);
|
||||
if (Factory == nullptr)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: factory creation failed (bug)\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the 'ParentClass' property.
|
||||
WingPropHandle Props;
|
||||
TSharedPtr<IPropertyHandle> PCProp = Props.NamedProperty(Factory, TEXT("ParentClass"), true, WingOut::Stdout);
|
||||
if (!PCProp) return;
|
||||
|
||||
// Store the parent class.
|
||||
FPropertyAccess::Result SetResult = PCProp->SetValue(ParentClassObj);
|
||||
if (SetResult != FPropertyAccess::Result::Success)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: property does not allow value: %s\n"), *ParentClass);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the asset using the factory.
|
||||
UObject *Blueprint = WingFactories::CreateAsset(Path, Factory, WingOut::Stdout);
|
||||
if (Blueprint == nullptr) return;
|
||||
WingOut::Stdout.Printf(TEXT("Created.\n"));
|
||||
}
|
||||
};
|
||||
303
Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h
Normal file
303
Plugins/UEWingman/Source/UEWingman/Handlers/Create_ClassArg.h
Normal file
@@ -0,0 +1,303 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Factories/Factory.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingTypes.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingFactories.h"
|
||||
#include "Create_ClassArg.generated.h"
|
||||
|
||||
struct FWingClassArgConfig
|
||||
{
|
||||
const TCHAR *Factory;
|
||||
const TCHAR *Base;
|
||||
bool ValidPointer = true; // eg, not nullptr
|
||||
bool ConcreteClass = false; // eg, not abstract class
|
||||
bool LatestVersion = true; // eg, not deprecated and no newer version.
|
||||
bool ShowInDropDown = true; // eg, not hide drop down
|
||||
bool NativeClass = false; // eg, not generated class
|
||||
bool TrueSubclass = false; // eg, not the base class itself
|
||||
bool CanCreateAsset = false; // calls 'CanCreateAssetOfClass'
|
||||
bool BlueprintBase = false; //
|
||||
bool Blueprintable = false; //
|
||||
bool CanCreateBlueprint = false; //
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UWingClassArg : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
FWingClassArgConfig Config;
|
||||
|
||||
UPROPERTY()
|
||||
TSubclassOf<UFactory> FactoryClass;
|
||||
|
||||
UPROPERTY()
|
||||
TObjectPtr<UClass> BaseClass;
|
||||
};
|
||||
|
||||
UCLASS()
|
||||
class UWing_Create_ClassArg : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
UPROPERTY(editAnywhere)
|
||||
FString Path;
|
||||
|
||||
UPROPERTY(EditAnywhere)
|
||||
FString Class;
|
||||
|
||||
public:
|
||||
void RegisterConfigs(TArrayView<const FWingClassArgConfig> Configs)
|
||||
{
|
||||
// Build a map of factory name -> UClass for quick lookup.
|
||||
TMap<FString, UClass*> FactoryMap;
|
||||
TArray<UClass*> FactoryClasses;
|
||||
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
|
||||
for (UClass* C : FactoryClasses) FactoryMap.Add(C->GetName(), C);
|
||||
|
||||
for (const FWingClassArgConfig& Cfg : Configs)
|
||||
{
|
||||
// Look up the factory class.
|
||||
UClass** FoundFactory = FactoryMap.Find(Cfg.Factory);
|
||||
if (!FoundFactory)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Create_ClassArg: factory '%s' not found, skipping"), Cfg.Factory);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Look up the base class.
|
||||
UClass* BaseClass = FindObject<UClass>(nullptr, Cfg.Base);
|
||||
if (!BaseClass)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Create_ClassArg: base class '%s' not found, skipping"), Cfg.Base);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the config object.
|
||||
UWingClassArg* Arg = NewObject<UWingClassArg>(this);
|
||||
Arg->Config = Cfg;
|
||||
Arg->FactoryClass = *FoundFactory;
|
||||
Arg->BaseClass = BaseClass;
|
||||
|
||||
// Register the command.
|
||||
FString FactoryName = WingFactories::DeriveFactoryName(*FoundFactory);
|
||||
FString CommandName = FString::Printf(TEXT("Create_%s"), *FactoryName);
|
||||
FString Doc = FString::Printf(TEXT("Create a new %s asset."), *FactoryName);
|
||||
UWingServer::AddHandler(this, CommandName, Arg, EWingHandlerKind::Create, *FoundFactory, Doc);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
RegisterConfigs({
|
||||
|
||||
// --- Standard candidates ---
|
||||
|
||||
{ .Factory = TEXT("AudioSynesthesiaNRTFactory"),
|
||||
.Base = TEXT("/Script/AudioSynesthesia.AudioSynesthesiaNRT"),
|
||||
.ConcreteClass = true },
|
||||
|
||||
{ .Factory = TEXT("AudioSynesthesiaNRTSettingsFactory"),
|
||||
.Base = TEXT("/Script/AudioSynesthesia.AudioSynesthesiaNRTSettings"),
|
||||
.ConcreteClass = true },
|
||||
|
||||
{ .Factory = TEXT("AudioSynesthesiaSettingsFactory"),
|
||||
.Base = TEXT("/Script/AudioSynesthesia.AudioSynesthesiaSettings"),
|
||||
.ConcreteClass = true },
|
||||
|
||||
{ .Factory = TEXT("CurveFactory"),
|
||||
.Base = TEXT("/Script/Engine.CurveBase"),
|
||||
.ConcreteClass = true, .CanCreateAsset = true },
|
||||
|
||||
{ .Factory = TEXT("DataAssetFactory"),
|
||||
.Base = TEXT("/Script/Engine.DataAsset"),
|
||||
.ConcreteClass = true, .CanCreateAsset = true },
|
||||
|
||||
{ .Factory = TEXT("InputAction_Factory"),
|
||||
.Base = TEXT("/Script/EnhancedInput.InputAction"),
|
||||
.ConcreteClass = true },
|
||||
|
||||
{ .Factory = TEXT("InputMappingContext_Factory"),
|
||||
.Base = TEXT("/Script/EnhancedInput.InputMappingContext"),
|
||||
.ConcreteClass = true },
|
||||
|
||||
{ .Factory = TEXT("PhysicalMaterialFactoryNew"),
|
||||
.Base = TEXT("/Script/PhysicsCore.PhysicalMaterial"),
|
||||
.ConcreteClass = true, .CanCreateAsset = true },
|
||||
|
||||
{ .Factory = TEXT("PhysicalMaterialMaskFactory"),
|
||||
.Base = TEXT("/Script/Engine.PhysicalMaterialMask"),
|
||||
.ConcreteClass = true, .CanCreateAsset = true },
|
||||
|
||||
{ .Factory = TEXT("SoundSourceEffectFactory"),
|
||||
.Base = TEXT("/Script/Engine.SoundEffectSourcePreset"),
|
||||
.ConcreteClass = true },
|
||||
|
||||
{ .Factory = TEXT("SoundSubmixEffectFactory"),
|
||||
.Base = TEXT("/Script/Engine.SoundEffectSubmixPreset"),
|
||||
.ConcreteClass = true },
|
||||
|
||||
// --- Slightly unusual ---
|
||||
|
||||
{ .Factory = TEXT("CameraAssetFactory"),
|
||||
.Base = TEXT("/Script/GameplayCameras.CameraDirector"),
|
||||
.ValidPointer = false, .ConcreteClass = true },
|
||||
|
||||
{ .Factory = TEXT("SlateWidgetStyleAssetFactory"),
|
||||
.Base = TEXT("/Script/SlateCore.SlateWidgetStyleContainerBase"),
|
||||
.ConcreteClass = true, .TrueSubclass = true },
|
||||
|
||||
{ .Factory = TEXT("TemplateSequenceFactoryNew"),
|
||||
.Base = TEXT("/Script/Engine.Actor") },
|
||||
|
||||
// --- Blueprint factories ---
|
||||
|
||||
{ .Factory = TEXT("BlueprintFactory"),
|
||||
.Base = TEXT("/Script/CoreUObject.Object"),
|
||||
.Blueprintable = true, .CanCreateBlueprint = true },
|
||||
|
||||
{ .Factory = TEXT("BlueprintMacroFactory"),
|
||||
.Base = TEXT("/Script/CoreUObject.Object"),
|
||||
.Blueprintable = true, .CanCreateBlueprint = true },
|
||||
|
||||
// BlueprintFunctionLibraryFactory is handled by Create_NoArgs.
|
||||
|
||||
// BlueprintInterfaceFactory is handled by Create_NoArgs.
|
||||
|
||||
{ .Factory = TEXT("WidgetBlueprintFactory"),
|
||||
.Base = TEXT("/Script/CoreUObject.Object"),
|
||||
.Blueprintable = true, .CanCreateBlueprint = true },
|
||||
|
||||
{ .Factory = TEXT("EditorUtilityBlueprintFactory"),
|
||||
.Base = TEXT("/Script/CoreUObject.Object"),
|
||||
.Blueprintable = true, .CanCreateBlueprint = true },
|
||||
|
||||
{ .Factory = TEXT("EditorUtilityWidgetBlueprintFactory"),
|
||||
.Base = TEXT("/Script/CoreUObject.Object"),
|
||||
.Blueprintable = true, .CanCreateBlueprint = true },
|
||||
|
||||
{ .Factory = TEXT("InterchangeBlueprintPipelineBaseFactory"),
|
||||
.Base = TEXT("/Script/InterchangeCore.InterchangePipelineBase"),
|
||||
.Blueprintable = true, .CanCreateBlueprint = true },
|
||||
|
||||
{ .Factory = TEXT("InterchangeEditorBlueprintPipelineBaseFactory"),
|
||||
.Base = TEXT("/Script/InterchangeEditorPipelines.InterchangeEditorPipelineBase"),
|
||||
.Blueprintable = true, .CanCreateBlueprint = true },
|
||||
|
||||
{ .Factory = TEXT("ControlRigBlueprintFactory"),
|
||||
.Base = TEXT("/Script/ControlRig.ControlRig"),
|
||||
.NativeClass = true, .BlueprintBase = true, .Blueprintable = true, .CanCreateBlueprint = true },
|
||||
});
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
// Get the configuration.
|
||||
UWingClassArg *ConfObj = Cast<UWingClassArg>(Configuration->Config.Get());
|
||||
if (ConfObj == nullptr)
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("ERROR: Create_ClassArg config object not set properly (bug)\n"));
|
||||
return;
|
||||
}
|
||||
FWingClassArgConfig &Config = ConfObj->Config;
|
||||
|
||||
// Verify that the specified path is good.
|
||||
if (!WingFactories::CheckNewAssetPath(Path, WingOut::Stdout)) return;
|
||||
|
||||
// Parse the class string.
|
||||
UClass *ClassObj = nullptr;
|
||||
if (!Class.IsEmpty())
|
||||
{
|
||||
UWingTypes::Requirements Req;
|
||||
Req.Blueprintable = Config.Blueprintable;
|
||||
Req.AllowContainer = false;
|
||||
Req.AllowNone = !Config.ValidPointer;
|
||||
Req.IsChildOf = ConfObj->BaseClass;
|
||||
FEdGraphPinType PinType;
|
||||
if (!UWingTypes::TextToType(Class, PinType, Req, WingOut::Stdout)) return;
|
||||
ClassObj = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
}
|
||||
|
||||
// Check constraints.
|
||||
if (Config.ValidPointer && (ClassObj == nullptr))
|
||||
{
|
||||
WingOut::Stdout.Print(TEXT("ERROR: factory requires a non-empty class to be specified."));
|
||||
return;
|
||||
}
|
||||
if (ClassObj != nullptr)
|
||||
{
|
||||
if (Config.ConcreteClass && ClassObj->HasAnyClassFlags(CLASS_Abstract))
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is abstract.\n"), *ClassObj->GetName());
|
||||
return;
|
||||
}
|
||||
if (Config.LatestVersion && ClassObj->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists))
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is deprecated or has a newer version.\n"), *ClassObj->GetName());
|
||||
return;
|
||||
}
|
||||
if (Config.ShowInDropDown && ClassObj->HasAnyClassFlags(CLASS_HideDropDown))
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is hidden.\n"), *ClassObj->GetName());
|
||||
return;
|
||||
}
|
||||
if (Config.NativeClass && !ClassObj->HasAnyClassFlags(CLASS_Native))
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is not a native class.\n"), *ClassObj->GetName());
|
||||
return;
|
||||
}
|
||||
if (Config.TrueSubclass && (ClassObj == ConfObj->BaseClass))
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is the base class itself, a subclass is required.\n"), *ClassObj->GetName());
|
||||
return;
|
||||
}
|
||||
if (Config.CanCreateAsset && !ClassObj->CanCreateAssetOfClass())
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: cannot create an asset of class '%s'.\n"), *ClassObj->GetName());
|
||||
return;
|
||||
}
|
||||
if (Config.BlueprintBase && !ClassObj->GetBoolMetaDataHierarchical(FBlueprintMetadata::MD_IsBlueprintBase))
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: class '%s' is not marked as a blueprint base.\n"), *ClassObj->GetName());
|
||||
return;
|
||||
}
|
||||
if (Config.CanCreateBlueprint && !FKismetEditorUtilities::CanCreateBlueprintOfClass(ClassObj))
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: cannot create a blueprint of class '%s'.\n"), *ClassObj->GetName());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Make the factory.
|
||||
UFactory *Factory = NewObject<UFactory>(GetTransientPackage(), ConfObj->FactoryClass);
|
||||
if (Factory == nullptr)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: factory creation failed (bug)\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the 'Class' property.
|
||||
TArray<FWingProperty> Props = FWingProperty::GetVisible(Factory);
|
||||
FWingProperty::Remove(Props, TEXT("BlueprintType"));
|
||||
if (Props.Num() != 1)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: Factory unexpectedly has multiple config properties (bug)\n"));
|
||||
return;
|
||||
}
|
||||
if (!CastField<FClassProperty>(Props[0].Prop))
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("ERROR: Factory was expected to have a 'class' property (bug)\n"));
|
||||
return;
|
||||
}
|
||||
if (!Props[0].SetObject(ClassObj, WingOut::Stdout)) return;
|
||||
|
||||
// Create the asset using the factory.
|
||||
UObject *Blueprint = WingFactories::CreateAsset(Path, Factory, WingOut::Stdout);
|
||||
if (Blueprint == nullptr) return;
|
||||
WingOut::Stdout.Printf(TEXT("Created.\n"));
|
||||
}
|
||||
};
|
||||
@@ -5,12 +5,11 @@
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingFactories.h"
|
||||
#include "WingPropHandle.h"
|
||||
#include "Factories/BlueprintFunctionLibraryFactory.h"
|
||||
#include "Create_UsingFactory.generated.h"
|
||||
#include "Create_NoArgs.generated.h"
|
||||
|
||||
UCLASS()
|
||||
class UWing_Create_UsingFactory : public UWingHandler
|
||||
class UWing_Create_NoArgs : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
@@ -22,32 +21,33 @@ public:
|
||||
{
|
||||
TArray<UClass*> FactoryClasses;
|
||||
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
|
||||
|
||||
for (UClass* Class : FactoryClasses)
|
||||
{
|
||||
if (Class->HasAnyClassFlags(CLASS_Abstract)) continue;
|
||||
|
||||
UFactory* CDO = Class->GetDefaultObject<UFactory>();
|
||||
if (!CDO->CanCreateNew() || !CDO->ShouldShowInNewMenu()) continue;
|
||||
|
||||
WingPropHandle Props;
|
||||
TArray<TSharedPtr<IPropertyHandle>> ConfigProps = Props.AllProperties(CDO, true);
|
||||
if (ConfigProps.Num() > 0) continue;
|
||||
if (!WingFactories::CanCreate(Class)) continue;
|
||||
if (WingFactories::GetParameterNames(Class).Num() > 0) continue;
|
||||
|
||||
FString FactoryName = WingFactories::DeriveFactoryName(Class);
|
||||
FString CommandName = FString::Printf(TEXT("Create_%s"), *FactoryName);
|
||||
FString Doc = FString::Printf(TEXT("Create a new %s asset."), *FactoryName);
|
||||
UWingServer::AddHandler(this, CommandName, Class, EWingHandlerKind::Create, Doc);
|
||||
UWingServer::AddHandler(this, CommandName, nullptr, EWingHandlerKind::Create, Class, Doc);
|
||||
}
|
||||
|
||||
UWingServer::AddHandler(this, TEXT("Create_BlueprintFunctionLibrary"),
|
||||
UBlueprintFunctionLibraryFactory::StaticClass(), EWingHandlerKind::Create,
|
||||
nullptr, EWingHandlerKind::Create,
|
||||
UBlueprintFunctionLibraryFactory::StaticClass(),
|
||||
TEXT("Create a blueprint function library"));
|
||||
|
||||
UClass *BlueprintInterfaceFactory = FindObject<UClass>(nullptr, TEXT("/Script/UnrealEd.BlueprintInterfaceFactory"));
|
||||
if (BlueprintInterfaceFactory)
|
||||
UWingServer::AddHandler(this, TEXT("Create_BlueprintInterface"),
|
||||
nullptr, EWingHandlerKind::Create,
|
||||
BlueprintInterfaceFactory,
|
||||
TEXT("Create a blueprint interface"));
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
UClass* FactoryClass = Cast<UClass>(ConfigurationObject);
|
||||
UClass* FactoryClass = Configuration->FactoryClass.Get();
|
||||
UFactory* Factory = NewObject<UFactory>(GetTransientPackage(), FactoryClass);
|
||||
WingFactories::CreateAsset(Path, Factory, WingOut::Stdout);
|
||||
WingOut::Stdout.Printf(TEXT("Created.\n"));
|
||||
@@ -6,7 +6,6 @@
|
||||
#include "WingFetcher.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingUtils.h"
|
||||
#include "WingTypes.h"
|
||||
#include "Details_Dump.generated.h"
|
||||
|
||||
UCLASS()
|
||||
@@ -30,7 +29,7 @@ public:
|
||||
UObject* Target = F.Walk(Object).Cast<UObject>();
|
||||
if (!Target) return;
|
||||
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Target, CPF_Edit, false);
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Target, false);
|
||||
|
||||
// Group by category, preserving within-category order.
|
||||
TSortedMap<FString, TArray<FWingProperty>> Categories;
|
||||
@@ -45,14 +44,7 @@ public:
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("\n%s:\n"), *Pair.Key);
|
||||
for (const FWingProperty& P : Pair.Value)
|
||||
{
|
||||
bool bEditable = !P->HasAnyPropertyFlags(CPF_EditConst);
|
||||
WingOut::Stdout.Printf(TEXT(" %s %s %s = %s\n"),
|
||||
bEditable ? TEXT("editable") : TEXT("readonly"),
|
||||
*UWingTypes::TypeToText(P.Prop),
|
||||
*WingUtils::FormatName(P.Prop),
|
||||
*P.GetTruncatedText(100));
|
||||
}
|
||||
P.Print(WingOut::Stdout);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,7 +32,7 @@ public:
|
||||
UObject* Obj = F.Walk(Object).Cast<UObject>();
|
||||
if (!Obj) return;
|
||||
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, CPF_Edit, false);
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, false);
|
||||
FWingProperty* P = WingUtils::FindOneWithExternalID(Property, Props, TEXT("Property"), WingOut::Stdout);
|
||||
if (!P) return;
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ public:
|
||||
UObject* Obj = F.Walk(Object).Cast<UObject>();
|
||||
if (!Obj) return;
|
||||
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, CPF_Edit, true);
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, true);
|
||||
FWingProperty* P = WingUtils::FindOneWithExternalID(Property, Props, TEXT("Property"), WingOut::Stdout);
|
||||
if (!P) return;
|
||||
|
||||
|
||||
if (P->SetText(Value, WingOut::Stdout))
|
||||
WingOut::Stdout.Print(TEXT("OK\n"));
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ public:
|
||||
return;
|
||||
}
|
||||
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, CPF_Edit, true);
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Obj, true);
|
||||
|
||||
// Validation pass — resolve all properties before modifying anything.
|
||||
for (const auto& Pair : Properties.Json->Values)
|
||||
|
||||
@@ -15,10 +15,10 @@ class UWing_Documentation_Commands : public UWingHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Optional, Description="Substring filter for command names"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Substring filter for command names"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
|
||||
bool Verbose = false;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -15,10 +15,10 @@ class UWing_Documentation_CreateAssets : public UWingHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Optional, Description="Substring filter for command names"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Substring filter for command names"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
|
||||
bool Verbose = false;
|
||||
|
||||
virtual void Register() override
|
||||
@@ -28,6 +28,6 @@ public:
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingManual::Commands(EWingHandlerKind::Normal, Query, Verbose);
|
||||
WingManual::Commands(EWingHandlerKind::Create, Query, Verbose);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -39,10 +39,9 @@ public:
|
||||
|
||||
FBPVariableDescription* Var = WingUtils::FindOneWithExternalID(Dispatcher, BP->NewVariables, TEXT("Dispatcher"), WingOut::Stdout);
|
||||
if (!Var) return;
|
||||
TObjectPtr<UEdGraph>* SigGraph = WingUtils::FindOneWithExternalID(Dispatcher, BP->DelegateSignatureGraphs, TEXT("Dispatcher Signature Graph"), WingOut::Stdout);
|
||||
if (!SigGraph) return;
|
||||
TObjectPtr<UEdGraph> Graph = WingUtils::FindOneWithExternalID(Dispatcher, BP->DelegateSignatureGraphs, TEXT("Dispatcher Signature Graph"), WingOut::Stdout);
|
||||
if (!Graph) return;
|
||||
|
||||
UEdGraph* Graph = *SigGraph;
|
||||
FName VarFName = Var->VarName;
|
||||
|
||||
// Remove the member variable (also destroys referencing nodes)
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
// Parse the json array, turning it into an array of spawn node entries.
|
||||
TArray<FSpawnNodeEntry> Entries;
|
||||
FSpawnNodeEntry Entry;
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry, CPF_None);
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry);
|
||||
for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array)
|
||||
{
|
||||
if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) return;
|
||||
|
||||
@@ -24,7 +24,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Query string, can contain *"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results (default 50)"))
|
||||
int32 MaxResults = 50;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target graph"))
|
||||
|
||||
@@ -91,7 +91,7 @@ public:
|
||||
UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>();
|
||||
if (!Node) return;
|
||||
|
||||
TArray<FWingProperty> All = FWingProperty::GetDetails(Node, CPF_Edit, true);
|
||||
TArray<FWingProperty> All = FWingProperty::GetDetails(Node, true);
|
||||
FWingProperty *P = WingUtils::FindOneWithExternalID(Entry.Name, All, TEXT("Property"), WingOut::Stdout);
|
||||
if (!P) return;
|
||||
|
||||
@@ -121,7 +121,7 @@ public:
|
||||
}
|
||||
|
||||
FSetNodeDefaultEntry Entry;
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry, CPF_None);
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry);
|
||||
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
|
||||
{
|
||||
if (!FWingProperty::PopulateFromJson(Props, *PinVal, false, WingOut::Stdout)) continue;
|
||||
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
int32 SuccessCount = 0;
|
||||
|
||||
FMoveNodeEntry Entry;
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry, CPF_None);
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(&Entry);
|
||||
for (const TSharedPtr<FJsonValue>& Elt : Nodes.Array)
|
||||
{
|
||||
if (!FWingProperty::PopulateFromJson(Props, *Elt, false, WingOut::Stdout)) continue;
|
||||
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
int32 TotalCount = Connections.Array.Num();
|
||||
|
||||
FConnectPinsEntry Entry;
|
||||
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry, CPF_None);
|
||||
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry);
|
||||
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
|
||||
{
|
||||
if (!FWingProperty::PopulateFromJson(EntryProps, *ConnVal, false, WingOut::Stdout))
|
||||
|
||||
@@ -24,7 +24,7 @@ struct FDisconnectPinEntry
|
||||
UPROPERTY()
|
||||
FString Pin;
|
||||
|
||||
UPROPERTY(meta=(Optional))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional))
|
||||
FString TargetPin;
|
||||
};
|
||||
|
||||
@@ -57,7 +57,7 @@ public:
|
||||
int32 TotalDisconnected = 0;
|
||||
|
||||
FDisconnectPinEntry Entry;
|
||||
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry, CPF_None);
|
||||
TArray<FWingProperty> EntryProps = FWingProperty::GetAll(&Entry);
|
||||
for (const TSharedPtr<FJsonValue>& DiscVal : Disconnections.Array)
|
||||
{
|
||||
if (!FWingProperty::PopulateFromJson(EntryProps, *DiscVal, false, WingOut::Stdout)) continue;
|
||||
|
||||
@@ -26,7 +26,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Path to graph"))
|
||||
FString Graph;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="True to include less-significant details"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="True to include less-significant details"))
|
||||
bool IncludeDetails;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -24,56 +24,27 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target material instance"))
|
||||
FString MaterialInstance;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Parameter name to clear"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Parameter ID: 'Name', 'Layer:N:Name', or 'Blend:N:Name'"))
|
||||
FString Parameter;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Parameter association: 'Global', 'Layer', or 'Blend'. Default: 'Global'", Optional))
|
||||
FString ParameterAssociation = TEXT("Global");
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Layer/blend index (0-based). Only used when ParameterAssociation is 'Layer' or 'Blend'", Optional))
|
||||
int32 ParameterLayer = INDEX_NONE;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Remove a parameter override from a Material Instance, reverting it to the parent material's value."));
|
||||
}
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingFetcher F(WingOut::Stdout);
|
||||
UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>();
|
||||
if (!MI) return;
|
||||
|
||||
// Parse the association string.
|
||||
EMaterialParameterAssociation Association;
|
||||
if (!WingMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association, WingOut::Stdout))
|
||||
// Parse the parameter ID.
|
||||
FMaterialParameterInfo ID;
|
||||
if (!WingMaterialParameter::ParseID(Parameter, ID, WingOut::Stdout))
|
||||
return;
|
||||
|
||||
FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer);
|
||||
|
||||
// Remove the override from all parameter arrays.
|
||||
auto RemoveFrom = [&](auto& Arr) {
|
||||
return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ParamID; });
|
||||
};
|
||||
|
||||
int32 Removed = 0;
|
||||
Removed += RemoveFrom(MI->ScalarParameterValues);
|
||||
Removed += RemoveFrom(MI->VectorParameterValues);
|
||||
Removed += RemoveFrom(MI->DoubleVectorParameterValues);
|
||||
Removed += RemoveFrom(MI->TextureParameterValues);
|
||||
Removed += RemoveFrom(MI->TextureCollectionParameterValues);
|
||||
Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues);
|
||||
Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues);
|
||||
Removed += RemoveFrom(MI->FontParameterValues);
|
||||
|
||||
if (Removed == 0)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("No override found for parameter '%s' (association=%s layer=%d) on %s"),
|
||||
*Parameter, *ParameterAssociation, ParameterLayer, *WingUtils::FormatName(MI));
|
||||
return;
|
||||
}
|
||||
|
||||
WingOut::Stdout.Printf(TEXT("Cleared override for '%s' on %s\n"),
|
||||
*Parameter, *WingUtils::FormatName(MI));
|
||||
if (!WingMaterialParameter::RemoveOverride(ID, MI, WingOut::Stdout)) return
|
||||
WingOut::Stdout.Printf(TEXT("Removed override\n"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -36,23 +36,7 @@ public:
|
||||
if (!MI) return;
|
||||
|
||||
auto AllParams = WingMaterialParameter::GetMaterialParameters(MI);
|
||||
|
||||
// Overridden parameters first.
|
||||
bool bHasOverrides = false;
|
||||
for (auto& [Info, Meta] : AllParams)
|
||||
{
|
||||
if (!Meta.bOverride) continue;
|
||||
if (!bHasOverrides) { WingOut::Stdout.Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; }
|
||||
WingMaterialParameter::FormatMaterialParameter(Info, Meta);
|
||||
}
|
||||
|
||||
// Inherited (non-overridden) parameters.
|
||||
bool bHasInherited = false;
|
||||
for (auto& [Info, Meta] : AllParams)
|
||||
{
|
||||
if (Meta.bOverride) continue;
|
||||
if (!bHasInherited) { WingOut::Stdout.Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; }
|
||||
WingMaterialParameter::FormatMaterialParameter(Info, Meta);
|
||||
}
|
||||
WingMaterialParameter::PrintAll(AllParams, true);
|
||||
if (AllParams.IsEmpty()) WingOut::Stdout.Printf(TEXT("No material parameters.\n"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -25,15 +25,9 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Target material instance"))
|
||||
FString MaterialInstance;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Parameter name to set"))
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Parameter ID: 'Name', 'Layer:N:Name', or 'Blend:N:Name'"))
|
||||
FString Parameter;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Parameter association: 'Global', 'Layer', or 'Blend'. Default: 'Global'", Optional))
|
||||
FString ParameterAssociation = TEXT("Global");
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Layer/blend index (0-based). Only used when ParameterAssociation is 'Layer' or 'Blend'", Optional))
|
||||
int32 ParameterLayer = INDEX_NONE;
|
||||
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Value to set (uses Unreal text format, e.g. '0.5' for scalar, '(R=1,G=0,B=0,A=1)' for vector)"))
|
||||
FString Value;
|
||||
|
||||
@@ -48,30 +42,19 @@ public:
|
||||
UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>();
|
||||
if (!MI) return;
|
||||
|
||||
// Parse the association string.
|
||||
EMaterialParameterAssociation Association;
|
||||
if (!WingMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association, WingOut::Stdout))
|
||||
// Parse the parameter ID.
|
||||
FMaterialParameterInfo ID;
|
||||
if (!WingMaterialParameter::ParseID(Parameter, ID, WingOut::Stdout))
|
||||
return;
|
||||
|
||||
// Build the parameter ID to look up.
|
||||
FMaterialParameterInfo ParamID(*Parameter, Association, ParameterLayer);
|
||||
|
||||
// Find it in the material's parameter map.
|
||||
auto AllParams = WingMaterialParameter::GetMaterialParameters(MI);
|
||||
FMaterialParameterMetadata* Found = AllParams.Find(ParamID);
|
||||
if (!Found)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("No parameter named '%s' with association=%s layer=%d"),
|
||||
*Parameter, *ParameterAssociation, ParameterLayer);
|
||||
return;
|
||||
}
|
||||
if (Found->PrimitiveDataIndex != INDEX_NONE)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("Parameter '%s' uses custom primitive data and cannot be set on a material instance"), *Parameter);
|
||||
return;
|
||||
}
|
||||
FMaterialParameterMetadata* Meta =
|
||||
WingMaterialParameter::FindParameter(AllParams, ID, WingOut::Stdout);
|
||||
if (!Meta) return;
|
||||
if (!WingMaterialParameter::CheckNoCustomPrimitiveData(ID, *Meta, WingOut::Stdout)) return;
|
||||
|
||||
EMaterialParameterType Type = Found->Value.Type;
|
||||
EMaterialParameterType Type = Meta->Value.Type;
|
||||
|
||||
switch (Type)
|
||||
{
|
||||
@@ -83,7 +66,7 @@ public:
|
||||
WingOut::Stdout.Printf(TEXT("Failed to parse '%s' as a float"), *Value);
|
||||
return;
|
||||
}
|
||||
MI->SetScalarParameterValueEditorOnly(ParamID, ScalarValue);
|
||||
MI->SetScalarParameterValueEditorOnly(ID, ScalarValue);
|
||||
break;
|
||||
}
|
||||
case EMaterialParameterType::Vector:
|
||||
@@ -94,14 +77,13 @@ public:
|
||||
WingOut::Stdout.Printf(TEXT("Failed to parse '%s' as a color/vector (expected format: '(R=1,G=0,B=0,A=1)')"), *Value);
|
||||
return;
|
||||
}
|
||||
MI->SetVectorParameterValueEditorOnly(ParamID, Color);
|
||||
MI->SetVectorParameterValueEditorOnly(ID, Color);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
WingOut::Stdout.Printf(TEXT("Parameters of type %d (see EMaterialParameterType) are not implemented"), (int)Type);
|
||||
return;
|
||||
}
|
||||
WingOut::Stdout.Printf(TEXT("Set '%s' = %s on %s\n"),
|
||||
*Parameter, *Value, *WingUtils::FormatName(MI));
|
||||
WingOut::Stdout.Printf(TEXT("Assigned.\n"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -35,11 +35,7 @@ public:
|
||||
if (!Mat) return;
|
||||
|
||||
auto AllParams = WingMaterialParameter::GetMaterialParameters(Mat);
|
||||
|
||||
for (auto& [Info, Meta] : AllParams)
|
||||
{
|
||||
WingMaterialParameter::FormatMaterialParameter(Info, Meta);
|
||||
}
|
||||
WingMaterialParameter::PrintAll(AllParams, false);
|
||||
if (AllParams.IsEmpty()) WingOut::Stdout.Printf(TEXT("No material parameters.\n"));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingProperty.h"
|
||||
#include "WingFactories.h"
|
||||
#include "SysInfo_Factories.generated.h"
|
||||
|
||||
@@ -28,30 +27,44 @@ public:
|
||||
|
||||
virtual void Handle() override
|
||||
{
|
||||
// Build a set of factory classes that are already supported by Create commands.
|
||||
TSet<UClass*> SupportedFactories;
|
||||
for (const FWingHandlerConfig& H : UWingServer::AllHandlers())
|
||||
{
|
||||
if (H.Kind != EWingHandlerKind::Create) continue;
|
||||
if (H.FactoryClass.IsValid())
|
||||
SupportedFactories.Add(H.FactoryClass.Get());
|
||||
}
|
||||
|
||||
TArray<UClass*> FactoryClasses;
|
||||
GetDerivedClasses(UFactory::StaticClass(), FactoryClasses);
|
||||
|
||||
TArray<FString> Results;
|
||||
TArray<FString> Supported;
|
||||
TArray<FString> Unsupported;
|
||||
|
||||
for (UClass *Factory : FactoryClasses)
|
||||
{
|
||||
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);
|
||||
if (!WingFactories::CanCreate(Factory)) continue;
|
||||
TArray<FName> Params = WingFactories::GetParameterNames(Factory);
|
||||
TStringBuilder<512> Line;
|
||||
Line.Appendf(TEXT("%2d %s "), Params.Num(), *Factory->GetName());
|
||||
for (const FName& Prop : Params)
|
||||
{
|
||||
Line.Appendf(TEXT(" %s"), *Prop.ToString());
|
||||
}
|
||||
Results.Add(Line.ToString());
|
||||
if (SupportedFactories.Contains(Factory))
|
||||
Supported.Add(Line.ToString());
|
||||
else
|
||||
Unsupported.Add(Line.ToString());
|
||||
}
|
||||
Results.Sort();
|
||||
for (const FString &Line : Results)
|
||||
{
|
||||
|
||||
Supported.Sort();
|
||||
Unsupported.Sort();
|
||||
|
||||
WingOut::Stdout.Print(TEXT("SUPPORTED:\n"));
|
||||
for (const FString &Line : Supported)
|
||||
WingOut::Stdout.Printf(TEXT("%s\n"), *Line);
|
||||
|
||||
WingOut::Stdout.Print(TEXT("\nUNSUPPORTED:\n"));
|
||||
for (const FString &Line : Unsupported)
|
||||
WingOut::Stdout.Printf(TEXT("%s\n"), *Line);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingTokenizer.h"
|
||||
#include "Test_Sanitizer.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UWing_Test_Sanitizer : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="The string to sanitize"))
|
||||
FString Input;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Test the sanitizer by sanitizing a string and printing the result."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("%s\n"), *WingTokenizer::ExternalizeID(FName(Input)));
|
||||
}
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingTokenizer.h"
|
||||
#include "Test_Tokenizer.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UWing_Test_Tokenizer : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="The string to tokenize"))
|
||||
FString Input;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Test the tokenizer by tokenizing a string and printing the result."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
WingTokenizer T(Input);
|
||||
T.PrintEverything(WingOut::StdoutBuffer);
|
||||
}
|
||||
};
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingTypes.h"
|
||||
#include "Test_TypeToText.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UWing_Test_TypeToText : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="The type name to parse, e.g. 'Array<Vector>'"))
|
||||
FString Input;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Test the type parser by parsing a type name and dumping the resulting FEdGraphPinType."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
FEdGraphPinType PinType;
|
||||
UWingTypes::Requirements Require;
|
||||
Require.BlueprintType = false;
|
||||
Require.Blueprintable = false;
|
||||
Require.AllowContainer = true;
|
||||
|
||||
bool OK = UWingTypes::TextToType(Input, PinType, Require, WingOut::Stdout);
|
||||
|
||||
auto& Out = WingOut::Stdout;
|
||||
Out.Printf(TEXT("ParseResult: %s\n"), OK ? TEXT("OK") : TEXT("FAILED"));
|
||||
Out.Printf(TEXT("PinCategory: %s\n"), *PinType.PinCategory.ToString());
|
||||
Out.Printf(TEXT("PinSubCategory: %s\n"), *PinType.PinSubCategory.ToString());
|
||||
Out.Printf(TEXT("PinSubCategoryObject: %s\n"),
|
||||
PinType.PinSubCategoryObject.IsValid()
|
||||
? *PinType.PinSubCategoryObject->GetPathName()
|
||||
: TEXT("(none)"));
|
||||
Out.Printf(TEXT("ContainerType: %d\n"), (int32)PinType.ContainerType);
|
||||
if (PinType.IsMap())
|
||||
{
|
||||
Out.Printf(TEXT("ValueTerminalCategory: %s\n"), *PinType.PinValueType.TerminalCategory.ToString());
|
||||
Out.Printf(TEXT("ValueTerminalSubCategory: %s\n"), *PinType.PinValueType.TerminalSubCategory.ToString());
|
||||
Out.Printf(TEXT("ValueTerminalSubCategoryObject: %s\n"),
|
||||
PinType.PinValueType.TerminalSubCategoryObject.IsValid()
|
||||
? *PinType.PinValueType.TerminalSubCategoryObject->GetPathName()
|
||||
: TEXT("(none)"));
|
||||
}
|
||||
|
||||
if (OK)
|
||||
{
|
||||
FString RoundTrip = UWingTypes::TypeToText(PinType);
|
||||
Out.Printf(TEXT("TypeToText: %s\n"), RoundTrip.IsEmpty() ? TEXT("(empty)") : *RoundTrip);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,41 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingTokenizer.h"
|
||||
#include "Test_Unsanitize.generated.h"
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
UCLASS()
|
||||
class UWing_Test_Unsanitize : public UWingHandler
|
||||
{
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="The sanitized identifier to unsanitize"))
|
||||
FString Input;
|
||||
|
||||
virtual void Register() override
|
||||
{
|
||||
UWingServer::AddHandler(this,
|
||||
TEXT("Test the unsanitizer by unsanitizing a string and printing the result."));
|
||||
}
|
||||
virtual void Handle() override
|
||||
{
|
||||
FString Error;
|
||||
FName Result = WingTokenizer::TryInternalizeID(Input, Error);
|
||||
if (!Error.IsEmpty())
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("Error: %s\n"), *Error);
|
||||
}
|
||||
if (!Result.IsNone())
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT("Result: %s\n"), *Result.ToString());
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -26,10 +26,10 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Substring filter for type names"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Maximum number of results"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results"))
|
||||
int32 Limit = 100;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="If true, include all types, not just BlueprintType/Blueprintable ones"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="If true, include all types, not just BlueprintType/Blueprintable ones"))
|
||||
bool Exhaustive = false;
|
||||
|
||||
virtual void Register() override
|
||||
@@ -43,23 +43,6 @@ public:
|
||||
return Info.IsUserDefined && Info.PinSubCategoryObject.StartsWith(TEXT("/Game/"));
|
||||
}
|
||||
|
||||
static FString BroadCategory(const UWingTypes::Info& Info)
|
||||
{
|
||||
if (Info.PinSubCategoryObject.IsEmpty()) return TEXT("Primitive");
|
||||
if (Info.PinCategory == UEdGraphSchema_K2::PC_Enum) return TEXT("Enum");
|
||||
if (Info.PinCategory == UEdGraphSchema_K2::PC_Struct) return TEXT("Struct");
|
||||
if (Info.PinCategory == UEdGraphSchema_K2::PC_Interface) return TEXT("Interface");
|
||||
if (Info.NativeParent)
|
||||
{
|
||||
if (Info.NativeParent->IsChildOf(UUserWidget::StaticClass())) return TEXT("Widget");
|
||||
if (Info.NativeParent->IsChildOf(UActorComponent::StaticClass())) return TEXT("ActorComponent");
|
||||
if (Info.NativeParent->IsChildOf(APawn::StaticClass())) return TEXT("Pawn");
|
||||
if (Info.NativeParent->IsChildOf(AActor::StaticClass())) return TEXT("Actor");
|
||||
if (Info.NativeParent->IsChildOf(UDataAsset::StaticClass())) return TEXT("DataAsset");
|
||||
}
|
||||
return TEXT("Object");
|
||||
}
|
||||
|
||||
static int Importance(const UWingTypes::Info& Info)
|
||||
{
|
||||
if (IsProjectDefined(Info)) return 1;
|
||||
@@ -94,9 +77,9 @@ public:
|
||||
{
|
||||
const UWingTypes::Info& Info = *Matches[i];
|
||||
if (IsProjectDefined(Info))
|
||||
WingOut::Stdout.Printf(TEXT("%s (%s, User-Defined)\n"), *Info.Short, *BroadCategory(Info));
|
||||
WingOut::Stdout.Printf(TEXT("%s (%s, User-Defined)\n"), *Info.Short, *Info.BroadCategory.ToString());
|
||||
else
|
||||
WingOut::Stdout.Printf(TEXT("%s (%s)\n"), *Info.Short, *BroadCategory(Info));
|
||||
WingOut::Stdout.Printf(TEXT("%s (%s)\n"), *Info.Short, *Info.BroadCategory.ToString());
|
||||
}
|
||||
if (Count >= Limit)
|
||||
{
|
||||
|
||||
@@ -21,16 +21,16 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Path to a blueprint, graph, or custom event node"))
|
||||
FString Object;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Blueprint variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Blueprint variables, one per line"))
|
||||
FString BlueprintVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Input variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variables, one per line"))
|
||||
FString InputVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Output variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variables, one per line"))
|
||||
FString OutputVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Local variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Local variables, one per line"))
|
||||
FString LocalVariables;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -21,16 +21,16 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Path to a blueprint, graph, or custom event node"))
|
||||
FString Object;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Blueprint variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Blueprint variables, one per line"))
|
||||
FString BlueprintVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Input variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variables, one per line"))
|
||||
FString InputVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Output variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variables, one per line"))
|
||||
FString OutputVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Local variables, one per line"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Local variables, one per line"))
|
||||
FString LocalVariables;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -21,16 +21,16 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Path to a blueprint, graph, or custom event node"))
|
||||
FString Object;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Blueprint variable names to remove, comma-separated"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Blueprint variable names to remove, comma-separated"))
|
||||
FString BlueprintVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Input variable names to remove, comma-separated"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Input variable names to remove, comma-separated"))
|
||||
FString InputVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Output variable names to remove, comma-separated"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Output variable names to remove, comma-separated"))
|
||||
FString OutputVariables;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Local variable names to remove, comma-separated"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Local variable names to remove, comma-separated"))
|
||||
FString LocalVariables;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -33,10 +33,10 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Name for the new widget"))
|
||||
FString Name;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Parent widget name. If omitted, sets as root."))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Parent widget name. If omitted, sets as root."))
|
||||
FString Parent;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Whether to expose the widget as a variable in the blueprint (default false)"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Whether to expose the widget as a variable in the blueprint (default false)"))
|
||||
bool IsVariable = false;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
UPROPERTY(EditAnywhere, meta=(Description="Query string, can contain *"))
|
||||
FString Query;
|
||||
|
||||
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)"))
|
||||
UPROPERTY(EditAnywhere, meta=(Optional, Description="Maximum number of results (default 50)"))
|
||||
int32 MaxResults = 50;
|
||||
|
||||
virtual void Register() override
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
#include "WingServer.h"
|
||||
#include "WingUtils.h"
|
||||
#include "PackageTools.h"
|
||||
#include "WingProperty.h"
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Kismet2/EnumEditorUtils.h"
|
||||
|
||||
@@ -30,7 +31,7 @@ bool WingFactories::CheckNewAssetPath(const FString& Path, WingOut Errors)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingFactories::IsBlacklisted(UClass* FactoryClass)
|
||||
bool WingFactories::IsBlacklisted(TSubclassOf<UFactory> FactoryClass)
|
||||
{
|
||||
FName Name = FactoryClass->GetFName();
|
||||
if (Name == TEXT("PhysicsAssetFactory")) return true; // Pops a modal dialog
|
||||
@@ -38,6 +39,20 @@ bool WingFactories::IsBlacklisted(UClass* FactoryClass)
|
||||
return false;
|
||||
}
|
||||
|
||||
bool WingFactories::CanCreate(TSubclassOf<UFactory> FactoryClass)
|
||||
{
|
||||
if (FactoryClass == nullptr) return false;
|
||||
if (FactoryClass->HasAnyClassFlags(CLASS_Abstract)) return false;
|
||||
if (IsBlacklisted(FactoryClass)) return false;
|
||||
UFactory* CDO = FactoryClass->GetDefaultObject<UFactory>();
|
||||
return CDO->CanCreateNew() && CDO->ShouldShowInNewMenu();
|
||||
}
|
||||
|
||||
TArray<FName> WingFactories::GetParameterNames(TSubclassOf<UFactory> FactoryClass)
|
||||
{
|
||||
return FWingProperty::GetVisibleNames(FactoryClass);
|
||||
}
|
||||
|
||||
UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, WingOut Errors)
|
||||
{
|
||||
// Check the blacklist.
|
||||
@@ -90,7 +105,7 @@ UObject* WingFactories::CreateAsset(const FString& Path, UFactory* Factory, Wing
|
||||
return NewAsset;
|
||||
}
|
||||
|
||||
FString WingFactories::DeriveFactoryName(UClass* FactoryClass)
|
||||
FString WingFactories::DeriveFactoryName(TSubclassOf<UFactory> FactoryClass)
|
||||
{
|
||||
FString Name = FactoryClass->GetName();
|
||||
int32 Index = Name.Find(TEXT("Factory"));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "WingFetcher.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingHandler.h"
|
||||
#include "WingUtils.h"
|
||||
#include "WingActorComponent.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
@@ -29,6 +30,7 @@ WingFetcher::WalkFunc WingFetcher::GetWalker(const FString& Step)
|
||||
if (Step.Equals(TEXT("component"), ESearchCase::IgnoreCase)) return &WingFetcher::Component;
|
||||
if (Step.Equals(TEXT("widget"), ESearchCase::IgnoreCase)) return &WingFetcher::Widget;
|
||||
if (Step.Equals(TEXT("levelblueprint"), ESearchCase::IgnoreCase)) return &WingFetcher::LevelBlueprint;
|
||||
if (Step.Equals(TEXT("structprop"), ESearchCase::IgnoreCase)) return &WingFetcher::StructProp;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -139,18 +141,16 @@ WingFetcher& WingFetcher::Asset(const FString& PackagePath)
|
||||
return SetError();
|
||||
}
|
||||
|
||||
// Check if the package exists before calling LoadObject, because
|
||||
// LoadObject logs its own errors when the package doesn't exist.
|
||||
FString PackageName = FPackageName::ObjectPathToPackageName(PackagePath);
|
||||
if (!FPackageName::DoesPackageExist(PackageName))
|
||||
UPackage* Package = LoadPackage(nullptr, *PackagePath, LOAD_NoWarn);
|
||||
if (!Package)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Asset '%s' does not exist.\n"), *PackagePath);
|
||||
return SetError();
|
||||
}
|
||||
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
|
||||
SetObj(Package->FindAssetInPackage());
|
||||
if (!Obj)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Could not load asset '%s'\n"), *PackagePath);
|
||||
Errors.Printf(TEXT("ERROR: Could not find asset in package '%s'\n"), *PackagePath);
|
||||
return SetError();
|
||||
}
|
||||
|
||||
@@ -372,3 +372,50 @@ WingFetcher& WingFetcher::LevelBlueprint(const FString& Value)
|
||||
SetObj(LevelBP);
|
||||
return *this;
|
||||
}
|
||||
|
||||
WingFetcher& WingFetcher::StructProp(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
FName InternalID = WingUtils::CheckInternalizeID(Value, Errors);
|
||||
if (InternalID.IsNone()) return SetError();
|
||||
|
||||
if (!Obj)
|
||||
{
|
||||
TypeMismatch(TEXT("structprop"), TEXT("UObject"));
|
||||
return SetError();
|
||||
}
|
||||
|
||||
FStructProperty* StructProp = nullptr;
|
||||
|
||||
// The "host" is the object containing this property.
|
||||
UObject *HostObject = Obj;
|
||||
void *HostBase = Obj;
|
||||
UStruct *HostType = Obj->GetClass();
|
||||
bool HostEditable = true;
|
||||
|
||||
// If we are *already* inside a UWingStructPointer, update the host
|
||||
// fields, to make it possible to navigate even further inside.
|
||||
if (UWingStructPointer *SPtr = ::Cast<UWingStructPointer>(Obj))
|
||||
{
|
||||
HostObject = SPtr->Object;
|
||||
HostBase = SPtr->StructBase;
|
||||
HostType = SPtr->StructType;
|
||||
HostEditable = SPtr->Editable;
|
||||
}
|
||||
|
||||
StructProp = FindFProperty<FStructProperty>(HostType, InternalID);
|
||||
if (!StructProp)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: No struct property '%s' found on %s\n"), *Value, *HostType->GetName());
|
||||
return SetError();
|
||||
}
|
||||
|
||||
UWingStructPointer* Ptr = NewObject<UWingStructPointer>();
|
||||
Ptr->Object = HostObject;
|
||||
Ptr->StructType = StructProp->Struct;
|
||||
Ptr->StructBase = StructProp->ContainerPtrToValuePtr<void>(HostBase);
|
||||
Ptr->Editable = HostEditable && StructProp->HasAllPropertyFlags(CPF_Edit);
|
||||
SetObj(Ptr);
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -204,7 +204,7 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node)
|
||||
Output.Appendf(TEXT("\nnode %s: %s\n"), *WingUtils::FormatName(Node), *WingUtils::FormatNodeTitle(Node));
|
||||
|
||||
// Emit node properties (if applicable).
|
||||
EmitNodeProperties(Node, Output, true);
|
||||
EmitNodeProperties(Node, &Output, true);
|
||||
|
||||
// Emit input data pins.
|
||||
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
|
||||
@@ -248,26 +248,25 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node)
|
||||
}
|
||||
}
|
||||
|
||||
void WingGraphExport::EmitNodeProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary)
|
||||
void WingGraphExport::EmitNodeProperties(UEdGraphNode* Node, WingOut Out, bool bPrimary)
|
||||
{
|
||||
TArray<FWingProperty> Props = FWingProperty::GetDetails(Node, false);
|
||||
|
||||
FString PrimaryCategory;
|
||||
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Node))
|
||||
{
|
||||
WingPropHandle::Handles Handles = Props.GetDetails(Node, false, WingOut::Stdout);
|
||||
PrimaryCategory = MatNode->MaterialExpression->GetClass()->GetName();
|
||||
for (const TSharedPtr<IPropertyHandle>& H : Handles)
|
||||
for (const FWingProperty& P : Props)
|
||||
{
|
||||
FString Category = H->GetProperty()->GetMetaData(TEXT("Category"));
|
||||
FString Category = P.GetCategory();
|
||||
if ((Category == PrimaryCategory) == bPrimary)
|
||||
WingPropHandle::Print(*H, Out);
|
||||
P.Print(Out);
|
||||
}
|
||||
}
|
||||
else if (bPrimary)
|
||||
{
|
||||
WingPropHandle::Handles Handles = Props.GetDetails(Node, false, WingOut::Stdout);
|
||||
for (const TSharedPtr<IPropertyHandle>& H : Handles)
|
||||
WingPropHandle::Print(*H, Out);
|
||||
for (const FWingProperty& P : Props)
|
||||
P.Print(Out);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +295,7 @@ void WingGraphExport::EmitDetails()
|
||||
Details.Appendf(TEXT("\ndetails %s\n"), *WingUtils::FormatName(Node));
|
||||
Details.Appendf(TEXT(" pos %d, %d\n"), Node->NodePosX, Node->NodePosY);
|
||||
|
||||
EmitNodeProperties(Node, Details, false);
|
||||
EmitNodeProperties(Node, &Details, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ void WingManual::PrintHandlerPrototype(const FWingHandlerConfig& Handler)
|
||||
WingOut::Stdout.Print(Handler.Name);
|
||||
WingOut::Stdout.Print(TEXT("("));
|
||||
bool bFirst = true;
|
||||
for (TFieldIterator<FProperty> PropIt(Handler.Class.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
for (TFieldIterator<FProperty> PropIt(Handler.HandlerClass.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
{
|
||||
if (!bFirst) WingOut::Stdout.Print(TEXT(","));
|
||||
bFirst = false;
|
||||
@@ -21,7 +21,7 @@ void WingManual::PrintHandlerPrototype(const FWingHandlerConfig& Handler)
|
||||
void WingManual::PrintHandlerArguments(const FWingHandlerConfig& Handler)
|
||||
{
|
||||
// parameter details
|
||||
for (TFieldIterator<FProperty> PropIt(Handler.Class.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
for (TFieldIterator<FProperty> PropIt(Handler.HandlerClass.Get(), EFieldIterationFlags::None); PropIt; ++PropIt)
|
||||
{
|
||||
FProperty* Prop = *PropIt;
|
||||
FString Name = Prop->GetName();
|
||||
@@ -306,7 +306,7 @@ void WingManual::Commands(EWingHandlerKind Kind, const FString& Query, bool Verb
|
||||
// Blank line between groups
|
||||
if (!Verbose)
|
||||
{
|
||||
FString Group = WingUtils::GetHandlerGroup(H.Class.Get());
|
||||
FString Group = WingUtils::GetHandlerGroup(H.HandlerClass.Get());
|
||||
if (Group != PrevGroup)
|
||||
{
|
||||
if (!PrevGroup.IsEmpty())
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
#include "WingMaterialParameter.h"
|
||||
#include "WingUtils.h"
|
||||
#include "WingTokenizer.h"
|
||||
#include "WingServer.h"
|
||||
|
||||
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> WingMaterialParameter::GetMaterialParameters(UMaterialInterface* Material)
|
||||
WingMaterialParameter::InfoMetaMap WingMaterialParameter::GetMaterialParameters(UMaterialInterface* Material)
|
||||
{
|
||||
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Result;
|
||||
InfoMetaMap Result;
|
||||
if (!Material) return Result;
|
||||
TMap<FMaterialParameterInfo, FMaterialParameterMetadata> Temp;
|
||||
InfoMetaMap Temp;
|
||||
for (int32 i = 0; i < (int32)EMaterialParameterType::NumRuntime; i++)
|
||||
{
|
||||
Material->GetAllParametersOfType((EMaterialParameterType)i, Temp);
|
||||
@@ -31,48 +32,183 @@ bool WingMaterialParameter::ParseMaterialParameterAssociation(const FString& Str
|
||||
return true;
|
||||
}
|
||||
|
||||
void WingMaterialParameter::FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta)
|
||||
void WingMaterialParameter::ReportBadToken(WingTokenizer &Tok, const TCHAR *Expected, WingOut Errors)
|
||||
{
|
||||
// Association prefix for layer/blend parameters.
|
||||
FString Prefix;
|
||||
if (Info.Association == LayerParameter)
|
||||
Prefix = FString::Printf(TEXT("[Layer %d] "), Info.Index);
|
||||
else if (Info.Association == BlendParameter)
|
||||
Prefix = FString::Printf(TEXT("[Blend %d] "), Info.Index);
|
||||
FString Whole = Tok.GetRange(FName(), 1000);
|
||||
Tok.SaveCursor(FName());
|
||||
FString Next = Tok.GetRange(FName(), 1);
|
||||
Errors.Printf(TEXT("Parsing %s, near %s: expected %s"), *Whole, *Next, Expected);
|
||||
}
|
||||
|
||||
switch (Meta.Value.Type)
|
||||
FString WingMaterialParameter::StringID(const FMaterialParameterInfo &ID)
|
||||
{
|
||||
FString Ext = WingUtils::ExternalizeID(ID.Name);
|
||||
if (ID.Association != EMaterialParameterAssociation::GlobalParameter)
|
||||
{
|
||||
case EMaterialParameterType::Scalar:
|
||||
WingOut::Stdout.Printf(TEXT(" %sScalar \"%s\" = %g\n"), *Prefix, *Info.Name.ToString(), Meta.Value.AsScalar());
|
||||
break;
|
||||
case EMaterialParameterType::Vector:
|
||||
{
|
||||
FLinearColor C = Meta.Value.AsLinearColor();
|
||||
WingOut::Stdout.Printf(TEXT(" %sVector \"%s\" = (R=%.3f, G=%.3f, B=%.3f, A=%.3f)\n"),
|
||||
*Prefix, *Info.Name.ToString(), C.R, C.G, C.B, C.A);
|
||||
break;
|
||||
const TCHAR *Prefix = TEXT("Layer");
|
||||
if (ID.Association == EMaterialParameterAssociation::BlendParameter)
|
||||
Prefix = TEXT("Blend");
|
||||
return FString::Printf(TEXT("%s:%d:%s"), Prefix, ID.Index, *Ext);
|
||||
}
|
||||
case EMaterialParameterType::DoubleVector:
|
||||
else
|
||||
{
|
||||
FVector4d V = Meta.Value.AsVector4d();
|
||||
WingOut::Stdout.Printf(TEXT(" %sDoubleVector \"%s\" = (%.3f, %.3f, %.3f, %.3f)\n"),
|
||||
*Prefix, *Info.Name.ToString(), V.X, V.Y, V.Z, V.W);
|
||||
break;
|
||||
}
|
||||
case EMaterialParameterType::Texture:
|
||||
{
|
||||
UTexture* Tex = Cast<UTexture>(Meta.Value.AsTextureObject());
|
||||
WingOut::Stdout.Printf(TEXT(" %sTexture \"%s\" = %s\n"),
|
||||
*Prefix, *Info.Name.ToString(), Tex ? *WingUtils::FormatName(Tex) : TEXT("None"));
|
||||
break;
|
||||
}
|
||||
case EMaterialParameterType::StaticSwitch:
|
||||
WingOut::Stdout.Printf(TEXT(" %sStaticSwitch \"%s\" = %s\n"),
|
||||
*Prefix, *Info.Name.ToString(), Meta.Value.AsStaticSwitch() ? TEXT("true") : TEXT("false"));
|
||||
break;
|
||||
default:
|
||||
WingOut::Stdout.Printf(TEXT(" %sType%d \"%s\"\n"), *Prefix, (int)Meta.Value.Type, *Info.Name.ToString());
|
||||
break;
|
||||
return Ext;
|
||||
}
|
||||
}
|
||||
|
||||
bool WingMaterialParameter::ParseID(const FString& IDString, FMaterialParameterInfo& ID, WingOut Errors)
|
||||
{
|
||||
WingTokenizer Tok(IDString);
|
||||
|
||||
ID.Association = EMaterialParameterAssociation::GlobalParameter;
|
||||
ID.Index = 0;
|
||||
ID.Name = FName();
|
||||
|
||||
// Parse the Layer or blend prefix.
|
||||
if (Tok.TokenIs(TEXT("Layer")) || Tok.TokenIs(TEXT("Blend")))
|
||||
{
|
||||
if (Tok.NextName() == TEXT("Layer"))
|
||||
ID.Association = EMaterialParameterAssociation::LayerParameter;
|
||||
else
|
||||
ID.Association = EMaterialParameterAssociation::BlendParameter;
|
||||
Tok.Advance();
|
||||
|
||||
if (!Tok.TokenIs(':'))
|
||||
{ ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
|
||||
Tok.Advance();
|
||||
|
||||
if ((!Tok.TokenIs(Tok.Identifier)) ||
|
||||
(!LexTryParseString(ID.Index, *Tok.NextName().ToString())))
|
||||
{ ReportBadToken(Tok, TEXT("integer"), Errors); return false; }
|
||||
Tok.Advance();
|
||||
|
||||
if ((ID.Index < 0)||(ID.Index > 50))
|
||||
{
|
||||
Errors.Printf(TEXT("Layer/Blend index outside of reasonable range\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Tok.TokenIs(':'))
|
||||
{ ReportBadToken(Tok, TEXT("colon"), Errors); return false; }
|
||||
Tok.Advance();
|
||||
}
|
||||
|
||||
// Parse the parameter name
|
||||
if (!Tok.TokenIs(Tok.Identifier))
|
||||
{ ReportBadToken(Tok, TEXT("parameter name"), Errors); return false; }
|
||||
ID.Name = Tok.NextName();
|
||||
Tok.Advance();
|
||||
|
||||
if (!Tok.TokenIs(0))
|
||||
{ ReportBadToken(Tok, TEXT("end of input"), Errors); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
FMaterialParameterMetadata *WingMaterialParameter::FindParameter(
|
||||
InfoMetaMap &Map, const FMaterialParameterInfo &ID, WingOut Errors)
|
||||
{
|
||||
FMaterialParameterMetadata* Found = Map.Find(ID);
|
||||
if (!Found)
|
||||
{
|
||||
Errors.Printf(TEXT("No parameter named '%s' in this material"), *StringID(ID));
|
||||
return nullptr;
|
||||
}
|
||||
return Found;
|
||||
}
|
||||
|
||||
bool WingMaterialParameter::CheckNoCustomPrimitiveData(
|
||||
const FMaterialParameterInfo &ID, const FMaterialParameterMetadata &Data, WingOut Errors)
|
||||
{
|
||||
if (Data.PrimitiveDataIndex != INDEX_NONE)
|
||||
{
|
||||
Errors.Printf(
|
||||
TEXT("Parameter '%s' uses custom primitive data, which cannot be set on a material instance"),
|
||||
*StringID(ID));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
FString WingMaterialParameter::FormatValue(const FMaterialParameterValue& Value)
|
||||
{
|
||||
switch (Value.Type)
|
||||
{
|
||||
case EMaterialParameterType::Scalar: {
|
||||
return FString::Printf(TEXT("scalar(%g)"), Value.AsScalar());
|
||||
}
|
||||
case EMaterialParameterType::Vector: {
|
||||
return FString::Printf(TEXT("vector(%s)"), *Value.AsLinearColor().ToString());
|
||||
}
|
||||
case EMaterialParameterType::DoubleVector: {
|
||||
return FString::Printf(TEXT("doublevector(%s)"), *Value.AsLinearColor().ToString());
|
||||
}
|
||||
case EMaterialParameterType::Texture: {
|
||||
UTexture* Tex = Cast<UTexture>(Value.AsTextureObject());
|
||||
return FString::Printf(TEXT("texture(%s)"), *Tex->GetPathName());
|
||||
}
|
||||
case EMaterialParameterType::StaticSwitch: {
|
||||
const TCHAR *Str = Value.AsStaticSwitch() ? TEXT("true") : TEXT("false");
|
||||
return FString::Printf(TEXT("staticswitch(%s)"), Str);
|
||||
}
|
||||
default: {
|
||||
return FString::Printf(TEXT("unknown(%d)"), (int)Value.Type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WingMaterialParameter::RemoveOverride(
|
||||
const FMaterialParameterInfo &ID, UMaterialInstanceConstant *MI, WingOut Errors)
|
||||
{
|
||||
// Remove the override from all parameter arrays.
|
||||
auto RemoveFrom = [&](auto& Arr) {
|
||||
return Arr.RemoveAll([&](auto& Entry) { return Entry.ParameterInfo == ID; });
|
||||
};
|
||||
|
||||
int32 Removed = 0;
|
||||
Removed += RemoveFrom(MI->ScalarParameterValues);
|
||||
Removed += RemoveFrom(MI->VectorParameterValues);
|
||||
Removed += RemoveFrom(MI->DoubleVectorParameterValues);
|
||||
Removed += RemoveFrom(MI->TextureParameterValues);
|
||||
Removed += RemoveFrom(MI->TextureCollectionParameterValues);
|
||||
Removed += RemoveFrom(MI->RuntimeVirtualTextureParameterValues);
|
||||
Removed += RemoveFrom(MI->SparseVolumeTextureParameterValues);
|
||||
Removed += RemoveFrom(MI->FontParameterValues);
|
||||
|
||||
if (Removed == 0)
|
||||
{
|
||||
Errors.Printf(TEXT("No override found for parameter '%s'"), *StringID(ID));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void WingMaterialParameter::Print(
|
||||
const FMaterialParameterInfo &Info, const FMaterialParameterMetadata &Meta)
|
||||
{
|
||||
WingOut::Stdout.Printf(TEXT(" %s %s\n"),
|
||||
*WingMaterialParameter::StringID(Info), *WingMaterialParameter::FormatValue(Meta.Value));
|
||||
}
|
||||
|
||||
void WingMaterialParameter::PrintAll(const InfoMetaMap &Params, bool Headings)
|
||||
{
|
||||
// Overridden parameters first.
|
||||
bool bHasOverrides = false;
|
||||
for (auto& [Info, Meta] : Params)
|
||||
{
|
||||
if (!Meta.bOverride) continue;
|
||||
if (Headings && !bHasOverrides)
|
||||
{ WingOut::Stdout.Print(TEXT("\nOverridden Parameters:\n")); bHasOverrides = true; }
|
||||
WingMaterialParameter::Print(Info, Meta);
|
||||
}
|
||||
|
||||
// Inherited (non-overridden) parameters.
|
||||
bool bHasInherited = false;
|
||||
for (auto& [Info, Meta] : Params)
|
||||
{
|
||||
if (Meta.bOverride) continue;
|
||||
if (Headings && !bHasInherited)
|
||||
{ WingOut::Stdout.Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; }
|
||||
WingMaterialParameter::Print(Info, Meta);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,524 +0,0 @@
|
||||
#include "WingPropHandle.h"
|
||||
#include "WingServer.h"
|
||||
#include "WingActorComponent.h"
|
||||
#include "WingUtils.h"
|
||||
#include "WingTypes.h"
|
||||
#include "Dom/JsonValue.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 "UObject/EnumProperty.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Get Root
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TSharedRef<IPropertyRowGenerator> WingPropHandle::CreateGenerator()
|
||||
{
|
||||
FPropertyEditorModule& Module = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||||
FPropertyRowGeneratorArgs Args;
|
||||
Args.bShouldShowHiddenProperties = false;
|
||||
return Module.CreatePropertyRowGenerator(Args);
|
||||
}
|
||||
|
||||
WingPropHandle::Root& WingPropHandle::GetRootForObject(UObject* Obj)
|
||||
{
|
||||
for (Root& R : Roots)
|
||||
{
|
||||
if (R.Base == (uint8*)Obj) return R;
|
||||
}
|
||||
TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
|
||||
Gen->SetObjects({Obj});
|
||||
Root& R = Roots.AddDefaulted_GetRef();
|
||||
R.Struct = Obj->GetClass();
|
||||
R.Base = (uint8*)Obj;
|
||||
R.End = R.Base + R.Struct->GetStructureSize();
|
||||
R.Generator = Gen;
|
||||
return R;
|
||||
}
|
||||
|
||||
WingPropHandle::Root& WingPropHandle::GetRootForStruct(const UStruct* ScriptStruct, uint8* Data)
|
||||
{
|
||||
for (Root& R : Roots)
|
||||
{
|
||||
if (R.Base == Data) return R;
|
||||
}
|
||||
TSharedRef<IPropertyRowGenerator> Gen = CreateGenerator();
|
||||
TSharedPtr<FStructOnScope> Wrapper = MakeShared<FStructOnScope>(ScriptStruct, Data);
|
||||
Gen->SetStructure(Wrapper);
|
||||
Root& R = Roots.AddDefaulted_GetRef();
|
||||
R.Struct = ScriptStruct;
|
||||
R.Base = Data;
|
||||
R.End = Data + ScriptStruct->GetStructureSize();
|
||||
R.Generator = Gen;
|
||||
return R;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// IsInsideRootObject
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool WingPropHandle::IsInsideRootObject(const Root& Root, IPropertyHandle& Handle)
|
||||
{
|
||||
// Walk up to the topmost property handle that still has a property.
|
||||
// Keep the shared pointers alive so the raw pointer stays valid.
|
||||
TSharedPtr<IPropertyHandle> Held;
|
||||
IPropertyHandle* Top = &Handle;
|
||||
while (true)
|
||||
{
|
||||
TSharedPtr<IPropertyHandle> Parent = Top->GetParentHandle();
|
||||
if (!Parent.IsValid() || !Parent->GetProperty()) break;
|
||||
Held = Parent;
|
||||
Top = Held.Get();
|
||||
}
|
||||
|
||||
// Get the address of the topmost property's data.
|
||||
void* Addr = nullptr;
|
||||
if (Top->GetValueData(Addr) != FPropertyAccess::Success || !Addr)
|
||||
return false;
|
||||
|
||||
uint8* DataPtr = (uint8*)Addr;
|
||||
return DataPtr >= Root.Base && DataPtr < Root.End;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// All Tree Nodes
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void WingPropHandle::AllTreeNodesRecursive(const TSharedRef<IDetailTreeNode>& Node, FlatTree& Out)
|
||||
{
|
||||
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(Root& Root)
|
||||
{
|
||||
FlatTree Result;
|
||||
for (const TSharedRef<IDetailTreeNode>& TreeRoot : Root.Generator->GetRootTreeNodes())
|
||||
{
|
||||
AllTreeNodesRecursive(TreeRoot, Result);
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// AllProperties
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
WingPropHandle::Handles WingPropHandle::AllProperties(Root& Root, bool RootFilter)
|
||||
{
|
||||
Handles Result;
|
||||
for (IDetailTreeNode* Node : AllTreeNodes(Root))
|
||||
{
|
||||
TSharedPtr<IPropertyHandle> Handle = Node->CreatePropertyHandle();
|
||||
if (Handle.IsValid() && Handle->GetProperty())
|
||||
{
|
||||
if (RootFilter && !IsInsideRootObject(Root, *Handle))
|
||||
continue;
|
||||
Result.Add(Handle);
|
||||
}
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
WingPropHandle::Handles WingPropHandle::AllProperties(UObject* Obj, bool RootFilter)
|
||||
{
|
||||
if (!Obj) return {};
|
||||
return AllProperties(GetRootForObject(Obj), RootFilter);
|
||||
}
|
||||
|
||||
WingPropHandle::Handles WingPropHandle::AllProperties(const UStruct* ScriptStruct, uint8* Data, bool RootFilter)
|
||||
{
|
||||
if (!ScriptStruct || !Data) return {};
|
||||
return AllProperties(GetRootForStruct(ScriptStruct, Data), RootFilter);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Named Property
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
TSharedPtr<IPropertyHandle> WingPropHandle::TryNamedProperty(Root& Root, FName Name, bool RootFilter)
|
||||
{
|
||||
for (IDetailTreeNode* Node : AllTreeNodes(Root))
|
||||
{
|
||||
if (Node->GetNodeName() != Name) continue;
|
||||
TSharedPtr<IPropertyHandle> Handle = Node->CreatePropertyHandle();
|
||||
if (Handle.IsValid() && (!RootFilter || IsInsideRootObject(Root, *Handle)))
|
||||
return Handle;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TSharedPtr<IPropertyHandle> WingPropHandle::TryNamedProperty(UObject* Obj, FName Name, bool RootFilter)
|
||||
{
|
||||
if (!Obj) return nullptr;
|
||||
return TryNamedProperty(GetRootForObject(Obj), Name, RootFilter);
|
||||
}
|
||||
|
||||
TSharedPtr<IPropertyHandle> WingPropHandle::TryNamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter)
|
||||
{
|
||||
if (!ScriptStruct || !Data) return nullptr;
|
||||
return TryNamedProperty(GetRootForStruct(ScriptStruct, Data), Name, RootFilter);
|
||||
}
|
||||
|
||||
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(UObject* Obj, FName Name, bool RootFilter, WingOut Errors)
|
||||
{
|
||||
TSharedPtr<IPropertyHandle> Result = TryNamedProperty(Obj, Name, RootFilter);
|
||||
if (!Result)
|
||||
Errors.Printf(TEXT("ERROR: Property '%s' not found\n"), *Name.ToString());
|
||||
return Result;
|
||||
}
|
||||
|
||||
TSharedPtr<IPropertyHandle> WingPropHandle::NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter, WingOut Errors)
|
||||
{
|
||||
TSharedPtr<IPropertyHandle> Result = TryNamedProperty(ScriptStruct, Data, Name, RootFilter);
|
||||
if (!Result)
|
||||
Errors.Printf(TEXT("ERROR: Property '%s' not found\n"), *Name.ToString());
|
||||
return Result;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// GetDetails
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
WingPropHandle::Handles WingPropHandle::GetDetails(UObject* Obj, bool Mutable, WingOut Errors)
|
||||
{
|
||||
bool RootFilter = false;
|
||||
|
||||
if (!Obj) return {};
|
||||
|
||||
// Blueprints: redirect to the generated class CDO.
|
||||
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
|
||||
{
|
||||
if (!BP->GeneratedClass)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Blueprint '%s' has no GeneratedClass\n"), *Obj->GetName());
|
||||
return {};
|
||||
}
|
||||
Obj = BP->GeneratedClass->GetDefaultObject();
|
||||
}
|
||||
|
||||
// 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<UWingComponentReference>(Obj))
|
||||
{
|
||||
Obj = Mutable ? Ref->GetMutableTemplate() : Ref->GetImmutableTemplate();
|
||||
if (!Obj)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Component '%s' has no template\n"), *Ref->VariableName.ToString());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
// Actors have components, which flood the property listing with hundreds
|
||||
// of confusing additional properties.
|
||||
if (Cast<AActor>(Obj)) RootFilter = true;
|
||||
|
||||
// Fetch the handles.
|
||||
Handles Result = AllProperties(Obj, RootFilter);
|
||||
|
||||
// Material graph nodes: also collect expression properties.
|
||||
if (UMaterialGraphNode* MatNode = Cast<UMaterialGraphNode>(Obj))
|
||||
{
|
||||
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
|
||||
{
|
||||
Result.Append(AllProperties(Expr, true));
|
||||
}
|
||||
}
|
||||
|
||||
// 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, false));
|
||||
// }
|
||||
// }
|
||||
|
||||
return Result;
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Organize by Name
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool WingPropHandle::OrganizeByName(const Handles &HList, TMap<FName, TSharedPtr<IPropertyHandle>> &Result, WingOut Errors)
|
||||
{
|
||||
Result.Empty();
|
||||
TSet<FName> DuplicateNames;
|
||||
for (const TSharedPtr<IPropertyHandle> &H : HList)
|
||||
{
|
||||
FName Name = H->GetProperty()->GetFName();
|
||||
if (Result.Contains(Name)) DuplicateNames.Add(Name);
|
||||
else Result.Add(Name, H);
|
||||
}
|
||||
if (DuplicateNames.IsEmpty()) return true;
|
||||
Errors.Print(TEXT("More than one property with name:"));
|
||||
for (FName DupName : DuplicateNames)
|
||||
{
|
||||
Errors.Printf(TEXT(" %s"), *WingUtils::ExternalizeID(DupName));
|
||||
}
|
||||
Errors.Print(TEXT("\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// GetText / SetText
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool WingPropHandle::IsPinTypeProperty(FProperty* Prop)
|
||||
{
|
||||
FStructProperty* StructProp = CastField<FStructProperty>(Prop);
|
||||
return StructProp && StructProp->Struct == FEdGraphPinType::StaticStruct();
|
||||
}
|
||||
|
||||
FString WingPropHandle::GetText(IPropertyHandle& Handle)
|
||||
{
|
||||
// Pin types: use our human-readable format.
|
||||
if (IsPinTypeProperty(Handle.GetProperty()))
|
||||
{
|
||||
void* Data = nullptr;
|
||||
if (Handle.GetValueData(Data) != FPropertyAccess::Success || !Data)
|
||||
UE_LOG(LogTemp, Fatal, TEXT("GetValueData failed for pin type property '%s'"),
|
||||
*Handle.GetProperty()->GetName());
|
||||
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'"),
|
||||
*Handle.GetProperty()->GetName());
|
||||
return Result;
|
||||
}
|
||||
|
||||
bool WingPropHandle::SetText(IPropertyHandle& Handle, const FString& Text, WingOut Errors)
|
||||
{
|
||||
FProperty* Prop = Handle.GetProperty();
|
||||
|
||||
// Pin types: parse with our type parser.
|
||||
if (IsPinTypeProperty(Prop))
|
||||
{
|
||||
FEdGraphPinType PinType;
|
||||
UWingTypes::Requirements Req;
|
||||
Req.BlueprintType = true;
|
||||
Req.Blueprintable = false;
|
||||
Req.AllowContainer = true;
|
||||
if (!UWingTypes::TextToType(Text, PinType, Req, Errors)) return false;
|
||||
void* Data = nullptr;
|
||||
if (Handle.GetValueData(Data) != FPropertyAccess::Success || !Data)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Cannot access data for property '%s'\n"),
|
||||
*WingUtils::FormatName(Prop));
|
||||
return false;
|
||||
}
|
||||
Handle.NotifyPreChange();
|
||||
*static_cast<FEdGraphPinType*>(Data) = PinType;
|
||||
Handle.NotifyPostChange(EPropertyChangeType::ValueSet);
|
||||
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, Errors);
|
||||
if (!Class) return false;
|
||||
}
|
||||
if (Handle.SetValue(Class) != FPropertyAccess::Success)
|
||||
{
|
||||
Errors.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;
|
||||
if (FByteProperty* ByteProp = CastField<FByteProperty>(Prop))
|
||||
Enum = ByteProp->Enum;
|
||||
if (FEnumProperty* EnumProp = CastField<FEnumProperty>(Prop))
|
||||
Enum = EnumProp->GetEnum();
|
||||
if (Enum != nullptr)
|
||||
{
|
||||
int64 EnumValue;
|
||||
if (!WingUtils::StringToEnum(Enum, Value, EnumValue, Errors)) return false;
|
||||
Value = Enum->GetNameStringByValue(EnumValue);
|
||||
}
|
||||
|
||||
FPropertyAccess::Result Result = Handle.SetValueFromFormattedString(Value);
|
||||
if (Result != FPropertyAccess::Success)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Failed to parse '%s' for property '%s' (type: %s)\n"),
|
||||
*Value, *WingUtils::FormatName(Prop), *Prop->GetCPPType());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SetJson
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
bool WingPropHandle::SetJson(IPropertyHandle& Handle, const TSharedPtr<FJsonValue>& JsonValue, WingOut Errors)
|
||||
{
|
||||
FPropertyAccess::Result Result;
|
||||
|
||||
switch (JsonValue->Type)
|
||||
{
|
||||
case EJson::String:
|
||||
return SetText(Handle, JsonValue->AsString(), Errors);
|
||||
|
||||
case EJson::Boolean:
|
||||
Result = Handle.SetValue(JsonValue->AsBool());
|
||||
break;
|
||||
|
||||
case EJson::Number:
|
||||
Result = Handle.SetValue(JsonValue->AsNumber());
|
||||
break;
|
||||
|
||||
default:
|
||||
Result = FPropertyAccess::Fail;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Result != FPropertyAccess::Success)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Failed to set property '%s'\n"),
|
||||
*WingUtils::FormatName(Handle.GetProperty()));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingPropHandle::PopulateFromJson(TArray<TSharedPtr<IPropertyHandle>>& Props, const FJsonObject& Json, bool AllOptional, WingOut Errors)
|
||||
{
|
||||
bool Ok = true;
|
||||
|
||||
// Organize the properties by name.
|
||||
TMap<FName, TSharedPtr<IPropertyHandle>> OrganizedProps;
|
||||
if (!OrganizeByName(Props, OrganizedProps, Errors)) Ok = false;
|
||||
|
||||
// Parse the keys in the json, make sure they're syntactically valid and
|
||||
// that they match the names of actual properties, and that there are no dups.
|
||||
TSet<FName> Specified;
|
||||
for (const auto& KV : Json.Values)
|
||||
{
|
||||
FName Name = WingUtils::CheckInternalizeID(KV.Key, Errors);
|
||||
if (Name.IsNone()) { Ok = false; continue; }
|
||||
if (!OrganizedProps.Contains(Name))
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Unknown parameter '%s'\n"), *KV.Key);
|
||||
Ok = false;
|
||||
}
|
||||
if (!WingUtils::FindNoDuplicateName(Specified, Name, TEXT("parameter"), Errors)) Ok = false;
|
||||
}
|
||||
|
||||
// Make sure that all required properties have been specified.
|
||||
if (!AllOptional)
|
||||
{
|
||||
for (const TSharedPtr<IPropertyHandle> &H : Props)
|
||||
{
|
||||
if (H->HasMetaData(TEXT("Optional"))) continue;
|
||||
FName Name = H->GetProperty()->GetFName();
|
||||
if (!Specified.Contains(Name))
|
||||
{
|
||||
Errors.Printf(TEXT("Required parameter %s not specified\n"),
|
||||
*WingUtils::ExternalizeID(Name));
|
||||
Ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If anything is wrong, return early without setting anything.
|
||||
if (!Ok) return false;
|
||||
|
||||
// Populate each property from JSON. This could fail too, but at this
|
||||
// point, we're committed.
|
||||
for (const auto& KV : Json.Values)
|
||||
{
|
||||
FName Name = WingUtils::CheckInternalizeID(KV.Key, Errors);
|
||||
if (!SetJson(*OrganizedProps[Name], KV.Value, Errors)) Ok = false;
|
||||
}
|
||||
return Ok;
|
||||
}
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Print
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void WingPropHandle::Print(IPropertyHandle& Handle, FStringBuilderBase& Out)
|
||||
{
|
||||
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(Handle.GetProperty()),
|
||||
*WingUtils::FormatName(Handle),
|
||||
*Value);
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
bool FWingProperty::SetObject(UObject *Obj, WingOut Errors) const
|
||||
{
|
||||
if (!CheckEditable(Errors)) return false;
|
||||
FObjectPropertyBase *OProp = CastField<FObjectPropertyBase>(Prop);
|
||||
if (!OProp)
|
||||
{
|
||||
@@ -45,94 +46,88 @@ bool FWingProperty::SetObject(UObject *Obj, WingOut Errors) const
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FWingProperty::SetDoubleInternal(
|
||||
FNumericProperty *NProp, void *Container, double D, WingOut Errors)
|
||||
{
|
||||
uint8 buffer[16];
|
||||
NProp->SetFloatingPointPropertyValue(buffer, D);
|
||||
if (!FMath::IsFinite(NProp->GetFloatingPointPropertyValue(buffer)))
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lf\n"),
|
||||
*WingUtils::FormatName(NProp), *NProp->GetCPPType(), D);
|
||||
return false;
|
||||
}
|
||||
NProp->SetValue_InContainer(Container, buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FWingProperty::SetInt64Internal(
|
||||
FNumericProperty *NProp, void *Container, int64 I, WingOut Errors)
|
||||
{
|
||||
uint8 buffer[16];
|
||||
if ((I < 0) && IsUnsigned(NProp))
|
||||
{
|
||||
Errors.Printf(TEXT(
|
||||
"ERROR: Cannot store signed %lld in unsigned property %s\n"),
|
||||
I, *WingUtils::FormatName(NProp));
|
||||
return false;
|
||||
}
|
||||
NProp->SetIntPropertyValue(buffer, I);
|
||||
if (NProp->GetSignedIntPropertyValue(buffer) != I)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lld\n"),
|
||||
*WingUtils::FormatName(NProp), *NProp->GetCPPType(), I);
|
||||
return false;
|
||||
}
|
||||
NProp->SetValue_InContainer(Container, buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FWingProperty::SetDouble(double D, WingOut Errors) const
|
||||
{
|
||||
if (!CheckEditable(Errors)) return false;
|
||||
FNumericProperty *NProp = CastField<FNumericProperty>(Prop);
|
||||
if (!NProp)
|
||||
if (NProp == nullptr)
|
||||
{
|
||||
PrintExpectsReceived(TEXT("double"), Errors);
|
||||
return false;
|
||||
}
|
||||
if (NProp->IsFloatingPoint())
|
||||
else if (NProp->IsFloatingPoint())
|
||||
{
|
||||
uint8 buffer[16];
|
||||
NProp->SetFloatingPointPropertyValue(buffer, D);
|
||||
if (!FMath::IsFinite(NProp->GetFloatingPointPropertyValue(buffer)))
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lf\n"),
|
||||
*WingUtils::FormatName(Prop), *Prop->GetCPPType(), D);
|
||||
return false;
|
||||
}
|
||||
Prop->SetValue_InContainer(Container, buffer);
|
||||
return true;
|
||||
return SetDoubleInternal(NProp, Container, D, Errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8 buffer[16];
|
||||
if (FMath::Floor(D) != D)
|
||||
{
|
||||
PrintExpectsReceived(TEXT("double"), Errors);
|
||||
return false;
|
||||
}
|
||||
if (FMath::Abs(D) > (double)((1LL)<<53))
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: To store very large numbers in '%s', do not pass them as double. Use string or int.\n"),
|
||||
*WingUtils::FormatName(Prop));
|
||||
return false;
|
||||
}
|
||||
int64 I = (int64)D;
|
||||
NProp->SetIntPropertyValue(buffer, I);
|
||||
if (NProp->GetSignedIntPropertyValue(buffer) != I)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lld\n"),
|
||||
*WingUtils::FormatName(Prop), *Prop->GetCPPType(), I);
|
||||
return false;
|
||||
}
|
||||
NProp->SetValue_InContainer(Container, buffer);
|
||||
return true;
|
||||
int64 I;
|
||||
if (!LosslessDoubleToInt64(D, I, Errors)) return false;
|
||||
return SetInt64Internal(NProp, Container, I, Errors);
|
||||
}
|
||||
}
|
||||
|
||||
bool FWingProperty::SetInt64(int64 I, WingOut Errors) const
|
||||
{
|
||||
if (!CheckEditable(Errors)) return false;
|
||||
FNumericProperty *NProp = CastField<FNumericProperty>(Prop);
|
||||
if (!NProp)
|
||||
if (NProp == nullptr)
|
||||
{
|
||||
PrintExpectsReceived(TEXT("int"), Errors);
|
||||
return false;
|
||||
}
|
||||
if (NProp->IsFloatingPoint())
|
||||
else if (NProp->IsFloatingPoint())
|
||||
{
|
||||
uint8 buffer[16];
|
||||
double D = I;
|
||||
NProp->SetFloatingPointPropertyValue(buffer, D);
|
||||
int64 RT = (int64)NProp->GetFloatingPointPropertyValue(buffer);
|
||||
if (RT != I)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lld\n"),
|
||||
*WingUtils::FormatName(Prop), *Prop->GetCPPType(), I);
|
||||
return false;
|
||||
}
|
||||
Prop->SetValue_InContainer(Container, buffer);
|
||||
return true;
|
||||
double D;
|
||||
if (!LosslessInt64ToDouble(I, D, Errors)) return false;
|
||||
return SetDoubleInternal(NProp, Container, D, Errors);
|
||||
}
|
||||
else
|
||||
{
|
||||
uint8 buffer[16];
|
||||
NProp->SetIntPropertyValue(buffer, I);
|
||||
if (NProp->GetSignedIntPropertyValue(buffer) != I)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Property '%s' of type %s cannot hold %lld\n"),
|
||||
*WingUtils::FormatName(Prop), *Prop->GetCPPType(), I);
|
||||
return false;
|
||||
}
|
||||
NProp->SetValue_InContainer(Container, buffer);
|
||||
return true;
|
||||
return SetInt64Internal(NProp, Container, I, Errors);
|
||||
}
|
||||
}
|
||||
|
||||
bool FWingProperty::SetBool(bool B, WingOut Errors) const
|
||||
{
|
||||
if (!CheckEditable(Errors)) return false;
|
||||
if (FBoolProperty* BoolProp = CastField<FBoolProperty>(Prop))
|
||||
{
|
||||
Prop->SetValue_InContainer(Container, &B);
|
||||
@@ -144,6 +139,8 @@ bool FWingProperty::SetBool(bool B, WingOut Errors) const
|
||||
|
||||
bool FWingProperty::SetText(FString Value, WingOut Errors) const
|
||||
{
|
||||
if (!CheckEditable(Errors)) return false;
|
||||
|
||||
// Pin types get parsed by UWingTypes.
|
||||
if (IsPinTypeProperty(Prop))
|
||||
{
|
||||
@@ -157,6 +154,21 @@ bool FWingProperty::SetText(FString Value, WingOut Errors) const
|
||||
return true;
|
||||
}
|
||||
|
||||
// Class properties get parsed by UWingTypes.
|
||||
if (FClassProperty *CProp = CastField<FClassProperty>(Prop))
|
||||
{
|
||||
UWingTypes::Requirements Req;
|
||||
Req.IsChildOf = CProp->MetaClass;
|
||||
if (CProp->MetaClass == nullptr) Req.IsChildOf = UObject::StaticClass();
|
||||
Req.AllowNone = true;
|
||||
Req.AllowContainer = false;
|
||||
FEdGraphPinType PinType;
|
||||
if (!UWingTypes::TextToType(Value, PinType, Req, Errors)) return false;
|
||||
CProp->SetObjectPropertyValue_InContainer(Container,
|
||||
Cast<UClass>(PinType.PinSubCategoryObject.Get()));
|
||||
return true;
|
||||
}
|
||||
|
||||
// If it's an enum type, use our parsing routine which is smarter about
|
||||
// prefixes than ImportText. We canonicalize the string, and then send
|
||||
// it onward to ImportText.
|
||||
@@ -186,6 +198,8 @@ bool FWingProperty::SetText(FString Value, WingOut Errors) const
|
||||
|
||||
bool FWingProperty::SetJson(const FJsonValue &JsonValue, WingOut Errors) const
|
||||
{
|
||||
if (!CheckEditable(Errors)) return false;
|
||||
|
||||
if (JsonValue.Type == EJson::String)
|
||||
{
|
||||
return SetText(JsonValue.AsString(), Errors);
|
||||
@@ -322,6 +336,12 @@ FString FWingProperty::GetText() const
|
||||
FEdGraphPinType *PinType = Prop->ContainerPtrToValuePtr<FEdGraphPinType>(Container);
|
||||
return UWingTypes::TypeToText(*PinType);
|
||||
}
|
||||
if (FClassProperty *CProp = CastField<FClassProperty>(Prop))
|
||||
{
|
||||
UObject *Obj = CProp->GetObjectPropertyValue_InContainer(Container);
|
||||
if (Obj) return UWingTypes::TypeToText(Obj);
|
||||
return TEXT("None");
|
||||
}
|
||||
FString Result;
|
||||
Prop->ExportTextItem_InContainer(Result, Container, nullptr, nullptr, PPF_None);
|
||||
return Result;
|
||||
@@ -338,6 +358,16 @@ FString FWingProperty::GetTruncatedText(int32 MaxLen) const
|
||||
}
|
||||
|
||||
|
||||
void FWingProperty::Print(WingOut Out) const
|
||||
{
|
||||
bool bEditable = !Prop->HasAnyPropertyFlags(CPF_EditConst);
|
||||
Out.Printf(TEXT(" %s %s %s = %s\n"),
|
||||
bEditable ? TEXT("editable") : TEXT("readonly"),
|
||||
*UWingTypes::TypeToText(Prop),
|
||||
*WingUtils::FormatName(Prop),
|
||||
*GetTruncatedText(100));
|
||||
}
|
||||
|
||||
FString FWingProperty::GetCategory() const
|
||||
{
|
||||
FString Result = Prop->GetMetaData(TEXT("Category"));
|
||||
@@ -345,28 +375,35 @@ FString FWingProperty::GetCategory() const
|
||||
return Result;
|
||||
}
|
||||
|
||||
void FWingProperty::GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags, TArray<FWingProperty> &Props)
|
||||
{
|
||||
for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
|
||||
{
|
||||
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
|
||||
Props.Add(FWingProperty(*It, Obj.StructPtr));
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FWingProperty> FWingProperty::GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags)
|
||||
TArray<FWingProperty> FWingProperty::GetAll(FWingStructAndUStruct Obj)
|
||||
{
|
||||
TArray<FWingProperty> Result;
|
||||
GetAll(Obj, Flags, Result);
|
||||
for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
|
||||
{
|
||||
bool Editable = !It->HasAnyPropertyFlags(CPF_EditConst);
|
||||
Result.Add(FWingProperty(*It, Obj.StructPtr, Editable));
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FName> FWingProperty::GetNames(UStruct *US, EPropertyFlags Flags)
|
||||
TArray<FWingProperty> FWingProperty::GetVisible(FWingStructAndUStruct Obj)
|
||||
{
|
||||
TArray<FWingProperty> Result;
|
||||
for (TFieldIterator<FProperty> It(Obj.UStructPtr); It; ++It)
|
||||
{
|
||||
if (!It->HasAllPropertyFlags(CPF_Edit)) continue;
|
||||
bool Editable = !It->HasAnyPropertyFlags(CPF_EditConst);
|
||||
Result.Add(FWingProperty(*It, Obj.StructPtr, Editable));
|
||||
}
|
||||
return Result;
|
||||
}
|
||||
|
||||
TArray<FName> FWingProperty::GetVisibleNames(UStruct *US)
|
||||
{
|
||||
TArray<FName> Result;
|
||||
for (TFieldIterator<FProperty> It(US); It; ++It)
|
||||
{
|
||||
if (Flags != 0 && !It->HasAnyPropertyFlags(Flags)) continue;
|
||||
if (!It->HasAllPropertyFlags(CPF_Edit)) continue;
|
||||
Result.Add(It->GetFName());
|
||||
}
|
||||
return Result;
|
||||
@@ -388,10 +425,20 @@ void FWingProperty::Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In,
|
||||
In.SetNum(Dst);
|
||||
}
|
||||
|
||||
TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Flags, bool Mutable)
|
||||
TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, bool Mutable)
|
||||
{
|
||||
if (!Obj) return {};
|
||||
|
||||
// If it's a UWingStructPointer, return the properties
|
||||
// of the struct instead. Propagate editability of the host.
|
||||
if (UWingStructPointer *SP = Cast<UWingStructPointer>(Obj))
|
||||
{
|
||||
TArray<FWingProperty> Result =
|
||||
GetVisible(FWingStructAndUStruct(SP->StructBase, SP->StructType));
|
||||
if (!Mutable || (!SP->Editable)) StripEditable(Result);
|
||||
return Result;
|
||||
}
|
||||
|
||||
// Blueprints don't have editable properties. So
|
||||
// instead, we fetch properties from the generated CDO.
|
||||
if (UBlueprint *BP = ::Cast<UBlueprint>(Obj))
|
||||
@@ -415,7 +462,7 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
|
||||
}
|
||||
}
|
||||
|
||||
TArray<FWingProperty> Result = GetAll(Obj, Flags);
|
||||
TArray<FWingProperty> Result = GetVisible(Obj);
|
||||
|
||||
// If it's a Material Graph node, also collect properties from
|
||||
// the associated material expression.
|
||||
@@ -424,7 +471,7 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
|
||||
{
|
||||
if (UMaterialExpression* Expr = MatNode->MaterialExpression)
|
||||
{
|
||||
GetAll(Expr, Flags, Result);
|
||||
Result.Append(GetVisible(Expr));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -435,10 +482,11 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, EPropertyFlags Fla
|
||||
FWingProperty::Remove(Result, TEXT("Slot"));
|
||||
if (UPanelSlot* Slot = Widget->Slot)
|
||||
{
|
||||
GetAll(Slot, Flags, Result);
|
||||
Result.Append(GetVisible(Slot));
|
||||
}
|
||||
}
|
||||
|
||||
if (!Mutable) StripEditable(Result);
|
||||
return Result;
|
||||
}
|
||||
|
||||
@@ -524,4 +572,59 @@ bool FWingProperty::CheckImportTextResult(const FString &Value, WingOut Errors)
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void FWingProperty::StripEditable(TArray<FWingProperty> &Props)
|
||||
{
|
||||
for (FWingProperty &Elt : Props) Elt.Editable = false;
|
||||
}
|
||||
|
||||
bool FWingProperty::CheckEditable(WingOut Errors) const
|
||||
{
|
||||
if (!Editable)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Cannot edit property %s, not marked editable"),
|
||||
*WingUtils::FormatName(Prop));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FWingProperty::IsUnsigned(FNumericProperty* Prop)
|
||||
{
|
||||
return
|
||||
CastField<FByteProperty>(Prop) ||
|
||||
CastField<FUInt16Property>(Prop) ||
|
||||
CastField<FUInt32Property>(Prop) ||
|
||||
CastField<FUInt64Property>(Prop);
|
||||
}
|
||||
|
||||
bool FWingProperty::LosslessDoubleToInt64(double D, int64 &I, WingOut Errors)
|
||||
{
|
||||
if (FMath::Floor(D) != D)
|
||||
{
|
||||
Errors.Printf(TEXT(
|
||||
"ERROR: Converting double %.4lf to integer would lose precision\n"), D);
|
||||
return false;
|
||||
}
|
||||
if (FMath::Abs(D) > (double)((1LL)<<53))
|
||||
{
|
||||
Errors.Printf(TEXT(
|
||||
"ERROR: Converting huge double %lf to integer would lose data.\n"), D);
|
||||
return false;
|
||||
}
|
||||
I = (int64)D;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FWingProperty::LosslessInt64ToDouble(int64 I, double &D, WingOut Errors)
|
||||
{
|
||||
D = (double)I;
|
||||
if ((int64)D != I)
|
||||
{
|
||||
Errors.Printf(TEXT(
|
||||
"ERROR: Converting huge integer %lld to floating point would lose data.\n"), I);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ void UWingServer::Initialize(FSubsystemCollectionBase& Collection)
|
||||
}
|
||||
|
||||
BuildWingHandlerRegistry();
|
||||
ModulesChangedHandle = FModuleManager::Get().OnModulesChanged().AddUObject(this, &UWingServer::OnModulesChanged);
|
||||
LogCapture.bEnabled = false;
|
||||
LogCapture.Install();
|
||||
bRunning = true;
|
||||
@@ -154,6 +155,8 @@ void UWingServer::Initialize(FSubsystemCollectionBase& Collection)
|
||||
|
||||
void UWingServer::Deinitialize()
|
||||
{
|
||||
FModuleManager::Get().OnModulesChanged().Remove(ModulesChangedHandle);
|
||||
|
||||
if (!bRunning)
|
||||
{
|
||||
Super::Deinitialize();
|
||||
@@ -323,12 +326,12 @@ void UWingServer::TryCallHandler(const FString &Line)
|
||||
LastHandler = Found;
|
||||
|
||||
// Make an object of the handler class.
|
||||
TStrongObjectPtr<UObject> HandlerObj(NewObject<UObject>(GetTransientPackage(), Found->Class.Get()));
|
||||
TStrongObjectPtr<UObject> HandlerObj(NewObject<UObject>(GetTransientPackage(), Found->HandlerClass.Get()));
|
||||
UWingHandler* Handler = Cast<UWingHandler>(HandlerObj.Get());
|
||||
Handler->ConfigurationObject = Found->Config.Get();
|
||||
Handler->Configuration = Found;
|
||||
|
||||
// Populate the handler object with the request parameters.
|
||||
TArray<FWingProperty> Props = FWingProperty::GetAll(Handler, CPF_Edit);
|
||||
TArray<FWingProperty> Props = FWingProperty::GetVisible(Handler);
|
||||
if (!FWingProperty::PopulateFromJson(Props, *Request, false, WingOut::Stdout))
|
||||
{
|
||||
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
|
||||
@@ -362,8 +365,6 @@ void UWingServer::AcceptNewConnections()
|
||||
Client->Socket = ClientSocket;
|
||||
Client->ThreadFuture = Async(EAsyncExecution::Thread, [this, Client]() { ClientThreadFunc(this, Client); });
|
||||
Clients.Add(Client);
|
||||
|
||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: Client connected."));
|
||||
}
|
||||
|
||||
void UWingServer::CleanupFinishedClients()
|
||||
@@ -383,71 +384,156 @@ void UWingServer::CleanupFinishedClients()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Stuff Performed on the Client Thread
|
||||
// ============================================================
|
||||
|
||||
void UWingServer::ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnection> Client)
|
||||
{
|
||||
constexpr int32 MaxRecvBufBytes = 1024 * 1024;
|
||||
constexpr int32 MinUnusedRecvSpace = 4096;
|
||||
|
||||
FSocket* Socket = Client->Socket;
|
||||
FString LineBuffer;
|
||||
uint8 RecvBuf[4096];
|
||||
TArray<uint8> RecvBuf;
|
||||
RecvBuf.SetNumUninitialized(MinUnusedRecvSpace);
|
||||
int32 RecvLen = 0;
|
||||
|
||||
WaitForAssetRegistry();
|
||||
|
||||
while (true)
|
||||
{
|
||||
int32 BytesRead = 0;
|
||||
if (!Socket->Recv(RecvBuf, sizeof(RecvBuf) - 1, BytesRead))
|
||||
FString Request;
|
||||
if (ExtractRequestFromBuffer(RecvBuf, RecvLen, Request))
|
||||
{
|
||||
break; // socket error or closed
|
||||
}
|
||||
if (BytesRead <= 0)
|
||||
{
|
||||
break; // connection closed
|
||||
}
|
||||
|
||||
RecvBuf[BytesRead] = 0;
|
||||
LineBuffer += UTF8_TO_TCHAR((const ANSICHAR*)RecvBuf);
|
||||
|
||||
// Process complete lines
|
||||
int32 NewlineIdx;
|
||||
while (LineBuffer.FindChar(TEXT('\n'), NewlineIdx))
|
||||
{
|
||||
FString Line = LineBuffer.Left(NewlineIdx).TrimEnd();
|
||||
LineBuffer.RightChopInline(NewlineIdx + 1);
|
||||
|
||||
if (Line.IsEmpty()) continue;
|
||||
|
||||
// Wait for the asset registry to finish its initial scan.
|
||||
FString Response;
|
||||
if (!ProcessRequestOnGameThread(Request, Response))
|
||||
{
|
||||
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
|
||||
while (AR.IsLoadingAssets()) FPlatformProcess::Sleep(0.25f);
|
||||
Client->bDone = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Enqueue the line for game-thread processing
|
||||
TSharedPtr<UWingServer::FPendingMessage> Msg = MakeShared<UWingServer::FPendingMessage>();
|
||||
Msg->Line = Line;
|
||||
TFuture<FString> Future = Msg->Response.GetFuture();
|
||||
|
||||
{
|
||||
FScopeLock Lock(&Server->Mutex);
|
||||
if (Server->bShuttingDown)
|
||||
{
|
||||
Client->bDone = true;
|
||||
return;
|
||||
}
|
||||
Server->PendingMessages.Add(Msg);
|
||||
}
|
||||
|
||||
// Block until the game thread processes this message
|
||||
FString Response = Future.Get();
|
||||
|
||||
// Write the response back, null-terminated (blocking)
|
||||
FTCHARToUTF8 Utf8(*Response);
|
||||
int32 BytesSent = 0;
|
||||
Socket->Send((const uint8*)Utf8.Get(), Utf8.Length() + 1, BytesSent);
|
||||
if (!SendAll(Socket, reinterpret_cast<const uint8*>(Utf8.Get()),
|
||||
Utf8.Length() + 1))
|
||||
{
|
||||
Client->bDone = true;
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ReceiveMoreBytesIntoBuffer(Socket, RecvBuf, RecvLen))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Client->bDone = true;
|
||||
UE_LOG(LogTemp, Display, TEXT("UEWingman: Client disconnected."));
|
||||
}
|
||||
|
||||
bool UWingServer::ExtractRequestFromBuffer(
|
||||
TArray<uint8>& RecvBuf, int32& RecvLen, FString& OutRequest)
|
||||
{
|
||||
const uint8* EndOfRequest = static_cast<const uint8*>(
|
||||
memchr(RecvBuf.GetData(), '\0', RecvLen));
|
||||
if (EndOfRequest == nullptr)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const int32 MessageLen =
|
||||
static_cast<int32>(EndOfRequest - RecvBuf.GetData());
|
||||
OutRequest = FString::ConstructFromPtrSize(
|
||||
reinterpret_cast<const UTF8CHAR*>(RecvBuf.GetData()), MessageLen);
|
||||
const int32 RemainingBytes = RecvLen - (MessageLen + 1);
|
||||
if (RemainingBytes > 0)
|
||||
{
|
||||
FMemory::Memmove(
|
||||
RecvBuf.GetData(),
|
||||
RecvBuf.GetData() + MessageLen + 1,
|
||||
RemainingBytes);
|
||||
}
|
||||
RecvLen = RemainingBytes;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UWingServer::ReceiveMoreBytesIntoBuffer(
|
||||
FSocket* Socket, TArray<uint8>& RecvBuf, int32& RecvLen)
|
||||
{
|
||||
constexpr int32 MaxRecvBufBytes = 1024 * 1024;
|
||||
constexpr int32 MinUnusedRecvSpace = 4096;
|
||||
|
||||
int32 UnusedSpace = RecvBuf.Num() - RecvLen;
|
||||
if (UnusedSpace < MinUnusedRecvSpace)
|
||||
{
|
||||
if (RecvBuf.Num() >= MaxRecvBufBytes)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
RecvBuf.SetNumUninitialized(RecvBuf.Num() * 2);
|
||||
UnusedSpace = RecvBuf.Num() - RecvLen;
|
||||
}
|
||||
|
||||
int32 BytesRead = 0;
|
||||
if (!Socket->Recv(RecvBuf.GetData() + RecvLen, UnusedSpace, BytesRead))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if (BytesRead <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
RecvLen += BytesRead;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UWingServer::SendAll(FSocket* Socket, const uint8* Data, int32 BytesToSend)
|
||||
{
|
||||
while (BytesToSend > 0)
|
||||
{
|
||||
int32 BytesSent = 0;
|
||||
if (!Socket->Send(Data, BytesToSend, BytesSent) || (BytesSent <= 0))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
Data += BytesSent;
|
||||
BytesToSend -= BytesSent;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UWingServer::ProcessRequestOnGameThread(
|
||||
const FString& Request, FString& Response)
|
||||
{
|
||||
// Enqueue the message for game-thread processing.
|
||||
TSharedPtr<UWingServer::FPendingMessage> Msg =
|
||||
MakeShared<UWingServer::FPendingMessage>();
|
||||
Msg->Line = Request;
|
||||
TFuture<FString> Future = Msg->Response.GetFuture();
|
||||
|
||||
{
|
||||
FScopeLock Lock(&GWingServer->Mutex);
|
||||
if (GWingServer->bShuttingDown)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
GWingServer->PendingMessages.Add(Msg);
|
||||
}
|
||||
|
||||
// Block until the game thread processes this message.
|
||||
Response = Future.Get();
|
||||
return true;
|
||||
}
|
||||
|
||||
void UWingServer::WaitForAssetRegistry()
|
||||
{
|
||||
IAssetRegistry& AR =
|
||||
FModuleManager::LoadModuleChecked<FAssetRegistryModule>(
|
||||
"AssetRegistry").Get();
|
||||
while (AR.IsLoadingAssets()) FPlatformProcess::Sleep(0.25f);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// BuildWingHandlerRegistry
|
||||
@@ -455,16 +541,17 @@ void UWingServer::ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnec
|
||||
|
||||
void UWingServer::AddHandler(UObject* Obj, const FString& Documentation)
|
||||
{
|
||||
AddHandler(Obj, WingUtils::GetHandlerName(Obj->GetClass()), nullptr, EWingHandlerKind::Normal, Documentation);
|
||||
AddHandler(Obj, WingUtils::GetHandlerName(Obj->GetClass()), nullptr, EWingHandlerKind::Normal, nullptr, Documentation);
|
||||
}
|
||||
|
||||
void UWingServer::AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, const FString& Documentation)
|
||||
void UWingServer::AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, UClass* FactoryClass, const FString& Documentation)
|
||||
{
|
||||
FWingHandlerConfig H;
|
||||
H.Name = Name;
|
||||
H.Documentation = Documentation;
|
||||
H.Class = TStrongObjectPtr<UClass>(Obj->GetClass());
|
||||
H.HandlerClass = TStrongObjectPtr<UClass>(Obj->GetClass());
|
||||
H.Config = TStrongObjectPtr<UObject>(Config);
|
||||
H.FactoryClass = TStrongObjectPtr<UClass>(FactoryClass);
|
||||
H.Kind = Kind;
|
||||
|
||||
GWingServer->WingHandlerRegistry.Add(MoveTemp(H));
|
||||
@@ -472,6 +559,7 @@ void UWingServer::AddHandler(UObject* Obj, const FString& Name, UObject* Config,
|
||||
|
||||
void UWingServer::BuildWingHandlerRegistry()
|
||||
{
|
||||
WingHandlerRegistry.Empty();
|
||||
for (UClass* Class : WingUtils::CollectHandlerClasses())
|
||||
{
|
||||
UWingHandler* CDO = Cast<UWingHandler>(Class->GetDefaultObject());
|
||||
@@ -480,6 +568,14 @@ void UWingServer::BuildWingHandlerRegistry()
|
||||
WingHandlerRegistry.Sort([](const FWingHandlerConfig& A, const FWingHandlerConfig& B) { return A.Name < B.Name; });
|
||||
}
|
||||
|
||||
void UWingServer::OnModulesChanged(FName ModuleName, EModuleChangeReason Reason)
|
||||
{
|
||||
if (Reason == EModuleChangeReason::ModuleLoaded)
|
||||
{
|
||||
BuildWingHandlerRegistry();
|
||||
}
|
||||
}
|
||||
|
||||
FWingHandlerConfig* UWingServer::FindHandler(const FString& Name)
|
||||
{
|
||||
int32 Index = Algo::LowerBoundBy(WingHandlerRegistry, Name, [](const FWingHandlerConfig& H) { return H.Name; });
|
||||
|
||||
@@ -249,16 +249,16 @@ void WingTokenizer::SaveCursor(FName Name)
|
||||
SavedCursor.Emplace(Name, Cursor);
|
||||
}
|
||||
|
||||
FStringView WingTokenizer::GetRange(FName SavePos, int Extra) const
|
||||
FString WingTokenizer::GetRange(FName SavePos, int Extra) const
|
||||
{
|
||||
int Lo = 0;
|
||||
for (auto &Pair : SavedCursor) if (Pair.Key == SavePos) Lo = Pair.Value;
|
||||
int Hi = (Next - Tokens.GetData()) + Extra;
|
||||
Hi = FMath::Clamp(Hi, Lo, Tokens.Num());
|
||||
if (Lo >= Hi) return FStringView();
|
||||
if (Lo >= Hi) return FString();
|
||||
const TCHAR* Start = Tokens[Lo].Source.GetData();
|
||||
const TCHAR* End = Tokens[Hi - 1].Source.GetData() + Tokens[Hi - 1].Source.Len();
|
||||
return FStringView(Start, End - Start);
|
||||
return FString(End - Start, Start);
|
||||
}
|
||||
|
||||
void WingTokenizer::PrintEverything(FStringBuilderBase &Out) const
|
||||
|
||||
@@ -12,8 +12,22 @@
|
||||
#include "AssetRegistry/AssetRegistryModule.h"
|
||||
#include "Blueprint/BlueprintSupport.h"
|
||||
#include "Kismet2/KismetEditorUtilities.h"
|
||||
#include "GameFramework/Actor.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "Blueprint/UserWidget.h"
|
||||
#include "GameFramework/Pawn.h"
|
||||
#include "Engine/DataAsset.h"
|
||||
|
||||
|
||||
static const FName NAME_TypeArray(TEXT("Array"));
|
||||
static const FName NAME_TypeSet(TEXT("Set"));
|
||||
static const FName NAME_TypeMap(TEXT("Map"));
|
||||
static const FName NAME_TypeSoft(TEXT("Soft"));
|
||||
static const FName NAME_TypeClass(TEXT("Class"));
|
||||
static const FName NAME_TypeSoftClass(TEXT("SoftClass"));
|
||||
static const FName NAME_StartOfType(TEXT("Start-of-Type"));
|
||||
static const FName NAME_NoneMeaningNullptr(TEXT("None"));
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Simple Accessors
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -128,6 +142,22 @@ const UClass *UWingTypes::FindNativeParent(const UClass *Obj)
|
||||
return Native;
|
||||
}
|
||||
|
||||
FName UWingTypes::BroadCategory(const UClass* Class)
|
||||
{
|
||||
// The nullptr case can really only happen if metadata is missing.
|
||||
//
|
||||
if (Class == nullptr) return("Unknown");
|
||||
|
||||
// Classify the object type.
|
||||
//
|
||||
if (Class->IsChildOf(UUserWidget::StaticClass())) return TEXT("Widget");
|
||||
if (Class->IsChildOf(UActorComponent::StaticClass())) return TEXT("ActorComponent");
|
||||
if (Class->IsChildOf(APawn::StaticClass())) return TEXT("Pawn");
|
||||
if (Class->IsChildOf(AActor::StaticClass())) return TEXT("Actor");
|
||||
if (Class->IsChildOf(UDataAsset::StaticClass())) return TEXT("DataAsset");
|
||||
return TEXT("Object");
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Choose Short Name
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -140,7 +170,7 @@ void UWingTypes::ReserveShortName(FName Name, FName PinCategory, FName PinSubCat
|
||||
Dummy.PinCategory = PinCategory;
|
||||
Dummy.PinSubCategory = PinSubCategory;
|
||||
Dummy.PinSubCategoryObject.Empty();
|
||||
Dummy.NativeParent = nullptr;
|
||||
Dummy.BroadCategory = TEXT("Primitive");
|
||||
Dummy.IsUserDefined = false;
|
||||
ShortToInfo.Add(NameStr.ToLower(), MoveTemp(Dummy));
|
||||
}
|
||||
@@ -157,7 +187,7 @@ FString UWingTypes::GetShortName(const FString &Path)
|
||||
return FString();
|
||||
}
|
||||
|
||||
FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, const UClass *NativeParent, bool IsUserDefined)
|
||||
FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, FName BroadCategory, bool IsUserDefined)
|
||||
{
|
||||
// Verify that the path is not already associated.
|
||||
check(!PathToShort.Find(Path));
|
||||
@@ -178,7 +208,7 @@ FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, const U
|
||||
Info TypeInfo;
|
||||
TypeInfo.PinCategory = PinCategory;
|
||||
TypeInfo.PinSubCategoryObject = Path;
|
||||
TypeInfo.NativeParent = NativeParent;
|
||||
TypeInfo.BroadCategory = BroadCategory;
|
||||
TypeInfo.IsUserDefined = IsUserDefined;
|
||||
|
||||
// Check if the proposed name is available.
|
||||
@@ -222,12 +252,14 @@ FString UWingTypes::ChooseShortName(const UObject* Obj)
|
||||
|
||||
if (Cast<UEnum>(Obj))
|
||||
{
|
||||
return NewShortName(Path, UEdGraphSchema_K2::PC_Enum, nullptr, Cast<UUserDefinedEnum>(Obj) != nullptr);
|
||||
return NewShortName(Path, UEdGraphSchema_K2::PC_Enum, TEXT("Enum"),
|
||||
Cast<UUserDefinedEnum>(Obj) != nullptr);
|
||||
}
|
||||
|
||||
if (Cast<UScriptStruct>(Obj))
|
||||
{
|
||||
return NewShortName(Path, UEdGraphSchema_K2::PC_Struct, nullptr, Cast<UUserDefinedStruct>(Obj) != nullptr);
|
||||
return NewShortName(Path, UEdGraphSchema_K2::PC_Struct, TEXT("Struct"),
|
||||
Cast<UUserDefinedStruct>(Obj) != nullptr);
|
||||
}
|
||||
|
||||
if (const UClass* Class = Cast<UClass>(Obj))
|
||||
@@ -235,7 +267,7 @@ FString UWingTypes::ChooseShortName(const UObject* Obj)
|
||||
bool IsInterface = Class->IsChildOf(UInterface::StaticClass());
|
||||
bool IsUserDefined = (Class->ClassGeneratedBy != nullptr);
|
||||
FName PinCategory = IsInterface ? UEdGraphSchema_K2::PC_Interface : UEdGraphSchema_K2::PC_Object;
|
||||
return NewShortName(Path, PinCategory, FindNativeParent(Class), IsUserDefined);
|
||||
return NewShortName(Path, PinCategory, BroadCategory(Class), IsUserDefined);
|
||||
}
|
||||
|
||||
return FString();
|
||||
@@ -252,7 +284,7 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
|
||||
{
|
||||
FString Path = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
|
||||
if (!GetShortName(Path).IsEmpty()) return;
|
||||
NewShortName(Path, UEdGraphSchema_K2::PC_Struct, nullptr, true);
|
||||
NewShortName(Path, UEdGraphSchema_K2::PC_Struct, TEXT("Struct"), true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -261,7 +293,7 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
|
||||
{
|
||||
FString Path = FString::Printf(TEXT("%s.%s"), *PackageName, *AssetName);
|
||||
if (!GetShortName(Path).IsEmpty()) return;
|
||||
NewShortName(Path, UEdGraphSchema_K2::PC_Enum, nullptr, true);
|
||||
NewShortName(Path, UEdGraphSchema_K2::PC_Enum, TEXT("Enum"), true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -285,12 +317,12 @@ void UWingTypes::ChooseShortName(const FAssetData &Data)
|
||||
if (BPType == TEXT("BPTYPE_Interface"))
|
||||
{
|
||||
check(NativeParent->IsChildOf(UInterface::StaticClass()));
|
||||
NewShortName(Path, UEdGraphSchema_K2::PC_Interface, NativeParent, true);
|
||||
NewShortName(Path, UEdGraphSchema_K2::PC_Interface, TEXT("Interface"), true);
|
||||
return;
|
||||
}
|
||||
else if (BPType == TEXT("BPTYPE_Normal") || BPType == TEXT("BPTYPE_Const"))
|
||||
{
|
||||
NewShortName(Path, UEdGraphSchema_K2::PC_Object, NativeParent, true);
|
||||
NewShortName(Path, UEdGraphSchema_K2::PC_Object, BroadCategory(NativeParent), true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -318,7 +350,8 @@ FString UWingTypes::TypeToText(FName Category, FName SubCategory, UObject* SubCa
|
||||
(Category == UEdGraphSchema_K2::PC_Int64) ||
|
||||
(Category == UEdGraphSchema_K2::PC_Name) ||
|
||||
(Category == UEdGraphSchema_K2::PC_String) ||
|
||||
(Category == UEdGraphSchema_K2::PC_Text))
|
||||
(Category == UEdGraphSchema_K2::PC_Text) ||
|
||||
(Category == NAME_NoneMeaningNullptr))
|
||||
{
|
||||
return Category.ToString();
|
||||
}
|
||||
@@ -408,6 +441,7 @@ FString UWingTypes::TypeToText(const UObject* Obj)
|
||||
{
|
||||
UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
|
||||
if (!Types) return FString();
|
||||
if (Obj == nullptr) return TEXT("None");
|
||||
return Types->ChooseShortName(Obj);
|
||||
}
|
||||
|
||||
@@ -415,6 +449,7 @@ FString UWingTypes::TypeToTextOrDie(const UObject* Obj)
|
||||
{
|
||||
UWingTypes* Types = GEditor->GetEditorSubsystem<UWingTypes>();
|
||||
if (!Types) return FString();
|
||||
if (Obj == nullptr) return TEXT("None");
|
||||
FString Result = Types->ChooseShortName(Obj);
|
||||
check(!Result.IsEmpty());
|
||||
return Result;
|
||||
@@ -424,15 +459,6 @@ FString UWingTypes::TypeToTextOrDie(const UObject* Obj)
|
||||
// Parser and Resolve Short Name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const FName NAME_TypeArray(TEXT("Array"));
|
||||
static const FName NAME_TypeSet(TEXT("Set"));
|
||||
static const FName NAME_TypeMap(TEXT("Map"));
|
||||
static const FName NAME_TypeSoft(TEXT("Soft"));
|
||||
static const FName NAME_TypeClass(TEXT("Class"));
|
||||
static const FName NAME_TypeSoftClass(TEXT("SoftClass"));
|
||||
|
||||
static const FName NAME_StartOfType("Start-of-Type");
|
||||
|
||||
void UWingTypes::PrintParseError(WingTokenizer& Tok, const TCHAR* Message, WingOut Errors)
|
||||
{
|
||||
FString TypeText(Tok.GetRange(NAME_StartOfType, 1));
|
||||
@@ -611,14 +637,6 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
|
||||
check(Types);
|
||||
Tok.SaveCursor(NAME_StartOfType);
|
||||
|
||||
if (!Require.BlueprintType.IsSet() ||
|
||||
!Require.Blueprintable.IsSet() ||
|
||||
!Require.AllowContainer.IsSet())
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: TextToType called with underspecified Requirements list.\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
OutPinType = FEdGraphPinType();
|
||||
if (!Types->ParseType(Tok, OutPinType, Errors))
|
||||
{
|
||||
@@ -631,7 +649,28 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
|
||||
OutPinType = FEdGraphPinType(); return false;
|
||||
}
|
||||
|
||||
if (!Require.AllowContainer.GetValue())
|
||||
// Check for none. If the type is none, and allownone is specified,
|
||||
// then that overrides a lot of other requirements. We set a flag for this.
|
||||
bool IsNone = OutPinType.PinCategory == NAME_NoneMeaningNullptr;
|
||||
bool AllowNoneIsOverriding = (Require.AllowNone && IsNone);
|
||||
|
||||
// None is never allowed inside a container.
|
||||
if (IsNone && OutPinType.IsContainer())
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: 'None' is not allowed in an array/set/map\n"));
|
||||
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
|
||||
OutPinType = FEdGraphPinType(); return false;
|
||||
}
|
||||
|
||||
// Don't allow none unless AllowNone is set.
|
||||
if (!Require.AllowNone && IsNone)
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: 'None' is not allowed here\n"));
|
||||
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
|
||||
OutPinType = FEdGraphPinType(); return false;
|
||||
}
|
||||
|
||||
if (!Require.AllowContainer)
|
||||
{
|
||||
if (OutPinType.IsContainer())
|
||||
{
|
||||
@@ -642,7 +681,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
|
||||
}
|
||||
}
|
||||
|
||||
if (!Require.PinCategory.IsNone())
|
||||
if (!Require.PinCategory.IsNone() && (!AllowNoneIsOverriding))
|
||||
{
|
||||
if (OutPinType.PinCategory != Require.PinCategory)
|
||||
{
|
||||
@@ -653,8 +692,17 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
|
||||
}
|
||||
}
|
||||
|
||||
if (Require.IsChildOf)
|
||||
if (Require.IsChildOf && (!AllowNoneIsOverriding))
|
||||
{
|
||||
// If the base class is not an interface, don't allow interfaces.
|
||||
if (!Require.IsChildOf->IsChildOf(UInterface::StaticClass())
|
||||
&& OutPinType.PinCategory == UEdGraphSchema_K2::PC_Interface)
|
||||
{
|
||||
FString Text(Tok.GetRange(NAME_StartOfType, 0));
|
||||
Errors.Printf(TEXT("ERROR: '%s' is an interface, not a class\n"), *Text);
|
||||
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
|
||||
OutPinType = FEdGraphPinType(); return false;
|
||||
}
|
||||
if (!IsChildOf(OutPinType, Require.IsChildOf))
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: Type must derive from %s\n"), *WingUtils::FormatName(Require.IsChildOf));
|
||||
@@ -663,7 +711,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
|
||||
}
|
||||
}
|
||||
|
||||
if (Require.BlueprintType.GetValue())
|
||||
if (Require.BlueprintType && (!AllowNoneIsOverriding))
|
||||
{
|
||||
if (!IsBlueprintType(OutPinType))
|
||||
{
|
||||
@@ -674,7 +722,7 @@ bool UWingTypes::TextToType(WingTokenizer &Tok, FEdGraphPinType& OutPinType, con
|
||||
}
|
||||
}
|
||||
|
||||
if (Require.Blueprintable.GetValue())
|
||||
if (Require.Blueprintable && (!AllowNoneIsOverriding))
|
||||
{
|
||||
if (!IsBlueprintable(OutPinType))
|
||||
{
|
||||
@@ -694,36 +742,6 @@ bool UWingTypes::TextToType(const FString &Text, FEdGraphPinType& OutPinType, co
|
||||
return TextToType(Tok, OutPinType, Require, true, Errors);
|
||||
}
|
||||
|
||||
UClass* UWingTypes::TextToOneObjectType(const FString& Text, const Requirements &Require, WingOut Errors)
|
||||
{
|
||||
FEdGraphPinType PinType;
|
||||
if (!TextToType(Text, PinType, Require, Errors)) return nullptr;
|
||||
UClass* Class = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
if ((!Class) || (PinType.PinCategory != UEdGraphSchema_K2::PC_Object) ||
|
||||
(PinType.IsContainer()))
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: '%s' is not an object class\n"), *Text);
|
||||
UWingServer::SuggestManual(WingManual::Section::Types);
|
||||
return nullptr;
|
||||
}
|
||||
return Class;
|
||||
}
|
||||
|
||||
UClass* UWingTypes::TextToOneInterfaceType(const FString& Text, const Requirements &Require, WingOut Errors)
|
||||
{
|
||||
FEdGraphPinType PinType;
|
||||
if (!TextToType(Text, PinType, Require, Errors)) return nullptr;
|
||||
UClass* Class = Cast<UClass>(PinType.PinSubCategoryObject.Get());
|
||||
if ((!Class) || (PinType.PinCategory != UEdGraphSchema_K2::PC_Interface) ||
|
||||
(PinType.IsContainer()))
|
||||
{
|
||||
Errors.Printf(TEXT("ERROR: '%s' is not an interface class\n"), *Text);
|
||||
UWingServer::SuggestManual(WingManual::Section::Types);
|
||||
return nullptr;
|
||||
}
|
||||
return Class;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Subsystem lifecycle
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -750,6 +768,7 @@ void UWingTypes::Initialize(FSubsystemCollectionBase& Collection)
|
||||
ReserveShortName(UEdGraphSchema_K2::PC_Name);
|
||||
ReserveShortName(UEdGraphSchema_K2::PC_String);
|
||||
ReserveShortName(UEdGraphSchema_K2::PC_Text);
|
||||
ReserveShortName(NAME_NoneMeaningNullptr);
|
||||
|
||||
// Scan priority packages first, then everything else in sorted order.
|
||||
ChooseShortNames(FindPackage(nullptr, TEXT("/Script/CoreUObject")));
|
||||
@@ -780,4 +799,3 @@ void UWingTypes::Deinitialize()
|
||||
}
|
||||
Super::Deinitialize();
|
||||
}
|
||||
|
||||
|
||||
@@ -274,12 +274,6 @@ FName WingUtils::GetFName(const FWingProperty &Prop)
|
||||
return Prop.Prop->GetFName();
|
||||
}
|
||||
|
||||
FName WingUtils::GetFName(const TSharedPtr<IPropertyHandle> &H)
|
||||
{
|
||||
return H->GetProperty()->GetFName();
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// Formatting other things
|
||||
// ============================================================
|
||||
@@ -385,21 +379,6 @@ bool WingUtils::CheckExactlyOneNamed(int Count, const TCHAR *Kind, FName Name, W
|
||||
return CheckExactlyOneNamed(Count, Kind, ExternalizeID(Name), Errors);
|
||||
}
|
||||
|
||||
bool WingUtils::CheckExactlyNoneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors)
|
||||
{
|
||||
if (Count > 0)
|
||||
{
|
||||
Errors.Printf(TEXT("A %s named %s already exists."), Kind, *Name);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WingUtils::CheckExactlyNoneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors)
|
||||
{
|
||||
return CheckExactlyNoneNamed(Count, Kind, ExternalizeID(Name), Errors);
|
||||
}
|
||||
|
||||
bool WingUtils::CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut Errors)
|
||||
{
|
||||
if (!Node->bCanRenameNode)
|
||||
|
||||
@@ -319,7 +319,7 @@ WingVariables::Var WingVariables::LoadBlueprintVariableDescription(FBPVariableDe
|
||||
FProperty* Prop = CDO->GetClass()->FindPropertyByName(Desc.VarName);
|
||||
if (Prop)
|
||||
{
|
||||
Result.DefaultValue = FWingProperty(Prop, CDO).GetText();
|
||||
Result.DefaultValue = FWingProperty(Prop, CDO, false).GetText();
|
||||
Result.DefaultSpecified = true;
|
||||
}
|
||||
}
|
||||
@@ -494,7 +494,7 @@ bool WingVariables::ModifyBlueprintDefaults(WingOut Errors)
|
||||
*WingTokenizer::ExternalizeID(Input.Name));
|
||||
return false;
|
||||
}
|
||||
if (!FWingProperty(Prop, CDO).SetText(Input.DefaultValue, Errors)) return false;
|
||||
if (!FWingProperty(Prop, CDO, true).SetText(Input.DefaultValue, Errors)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
@@ -538,11 +538,11 @@ bool WingVariables::ModifyEditablePinBase(WingVariableList &List, UK2Node_Editab
|
||||
{
|
||||
for (Var &V : List.Variables)
|
||||
{
|
||||
TSharedPtr<FUserPinInfo> *Found =
|
||||
TSharedPtr<FUserPinInfo> Found =
|
||||
WingUtils::FindOneWithInternalID(V.Name, Node->UserDefinedPins, List.ListName, Errors);
|
||||
if (!Found) return false;
|
||||
(*Found)->PinType = V.Type;
|
||||
if (V.DefaultSpecified) (*Found)->PinDefaultValue = V.DefaultValue;
|
||||
Found->PinType = V.Type;
|
||||
if (V.DefaultSpecified) Found->PinDefaultValue = V.DefaultValue;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -736,13 +736,13 @@ bool WingVariables::RemoveGraph(WingOut Errors)
|
||||
// Verify that all named variables exist before removing anything.
|
||||
for (const Var& V : InputVariables.Variables)
|
||||
{
|
||||
TSharedPtr<FUserPinInfo>* Found =
|
||||
TSharedPtr<FUserPinInfo> Found =
|
||||
WingUtils::FindOneWithInternalID(V.Name, InputNode->UserDefinedPins, TEXT("input variable"), Errors);
|
||||
if (!Found) return false;
|
||||
}
|
||||
for (const Var& V : OutputVariables.Variables)
|
||||
{
|
||||
TSharedPtr<FUserPinInfo>* Found =
|
||||
TSharedPtr<FUserPinInfo> Found =
|
||||
WingUtils::FindOneWithInternalID(V.Name, OutputNode->UserDefinedPins, TEXT("output variable"), Errors);
|
||||
if (!Found) return false;
|
||||
}
|
||||
@@ -778,7 +778,7 @@ bool WingVariables::RemoveCustomEvent(WingOut Errors)
|
||||
// Verify that all named parameters exist before removing anything.
|
||||
for (const Var& V : InputVariables.Variables)
|
||||
{
|
||||
TSharedPtr<FUserPinInfo>* Found =
|
||||
TSharedPtr<FUserPinInfo> Found =
|
||||
WingUtils::FindOneWithInternalID(V.Name, CustomEvent->UserDefinedPins, TEXT("event parameter"), Errors);
|
||||
if (!Found) return false;
|
||||
}
|
||||
|
||||
@@ -20,12 +20,20 @@ public:
|
||||
// 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);
|
||||
static bool IsBlacklisted(TSubclassOf<UFactory> FactoryClass);
|
||||
|
||||
// Check if the factory class can be used to create assets.
|
||||
// makes sure it's not abstract, calls CanCreateNew,
|
||||
// calls ShouldShowInNewMenu, and verifies not blacklisted.
|
||||
static bool CanCreate(TSubclassOf<UFactory> FactoryClass);
|
||||
|
||||
// Get the names of the editable properties for a factory class.
|
||||
static TArray<FName> GetParameterNames(TSubclassOf<UFactory> 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(TSubclassOf<UFactory> FactoryClass);
|
||||
|
||||
// Verifies that the asset path is a valid path, and also
|
||||
// that there's not something already there at that path.
|
||||
|
||||
@@ -54,6 +54,7 @@ public:
|
||||
WingFetcher& Component(const FString& Value);
|
||||
WingFetcher& Widget(const FString& Value);
|
||||
WingFetcher& LevelBlueprint(const FString& Value);
|
||||
WingFetcher& StructProp(const FString& Value);
|
||||
|
||||
// Return true if there haven't been any errors.
|
||||
// Note that errors always automatically generate
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingPropHandle.h"
|
||||
#include "WingProperty.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
@@ -62,7 +62,7 @@ private:
|
||||
void Traverse(UEdGraphNode* Node);
|
||||
void SortNodes();
|
||||
void EmitNode(UEdGraphNode* Node);
|
||||
void EmitNodeProperties(UEdGraphNode* Node, FStringBuilderBase& Out, bool bPrimary);
|
||||
void EmitNodeProperties(UEdGraphNode* Node, WingOut Out, bool bPrimary);
|
||||
void EmitLocalVariables();
|
||||
void EmitGraph();
|
||||
void EmitDetails();
|
||||
@@ -76,7 +76,6 @@ private:
|
||||
|
||||
|
||||
UEdGraph* Graph;
|
||||
WingPropHandle Props;
|
||||
|
||||
// Data populated by passes.
|
||||
TArray<UEdGraphNode*> SortedNodes;
|
||||
|
||||
@@ -39,8 +39,9 @@ struct FWingHandlerConfig
|
||||
GENERATED_BODY()
|
||||
FString Name;
|
||||
FString Documentation;
|
||||
TStrongObjectPtr<UClass> Class;
|
||||
TStrongObjectPtr<UClass> HandlerClass;
|
||||
TStrongObjectPtr<UObject> Config;
|
||||
TStrongObjectPtr<UClass> FactoryClass;
|
||||
EWingHandlerKind Kind = EWingHandlerKind::Normal;
|
||||
};
|
||||
|
||||
@@ -57,7 +58,7 @@ public:
|
||||
virtual void Handle() {}
|
||||
|
||||
// The configuration object.
|
||||
UObject *ConfigurationObject;
|
||||
FWingHandlerConfig *Configuration;
|
||||
|
||||
public:
|
||||
};
|
||||
@@ -93,6 +94,9 @@ struct FWingStructAndUStruct
|
||||
void *StructPtr;
|
||||
UStruct *UStructPtr;
|
||||
|
||||
// Explicit constructor.
|
||||
explicit FWingStructAndUStruct(void *Base, UStruct *S) : StructPtr(Base), UStructPtr(S) {}
|
||||
|
||||
// Copy constructor.
|
||||
FWingStructAndUStruct(FWingStructAndUStruct &Src) : StructPtr(Src.StructPtr), UStructPtr(Src.UStructPtr) {}
|
||||
|
||||
@@ -102,4 +106,29 @@ struct FWingStructAndUStruct
|
||||
// Construct from a UStruct pointer.
|
||||
template<class T, typename = std::enable_if_t<!std::is_base_of_v<UObject, T>>>
|
||||
FWingStructAndUStruct(T *Struct) : StructPtr(Struct), UStructPtr(Struct->StaticStruct()) {}
|
||||
};
|
||||
|
||||
|
||||
// WingStructPointer: Allows us to store a pointer to a
|
||||
// ustruct in a variable of class UObject, through the magic
|
||||
// of an extra level of indirection. If the struct is part of
|
||||
// a UObject, then the Object field should be point at the
|
||||
// object to ensure it doesn't get garbage collected. If the
|
||||
// editable flag is false, it means the struct is for viewing
|
||||
// only.
|
||||
|
||||
UCLASS()
|
||||
class UWingStructPointer : public UObject
|
||||
{
|
||||
GENERATED_BODY()
|
||||
public:
|
||||
UPROPERTY()
|
||||
UObject* Object;
|
||||
|
||||
UPROPERTY()
|
||||
UStruct* StructType;
|
||||
|
||||
void *StructBase;
|
||||
|
||||
bool Editable;
|
||||
};
|
||||
@@ -2,13 +2,63 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "Materials/Material.h"
|
||||
#include "WingTokenizer.h"
|
||||
#include "Materials/MaterialInstanceConstant.h"
|
||||
#include "WingHandler.h"
|
||||
|
||||
class WingMaterialParameter
|
||||
{
|
||||
public:
|
||||
static TMap<FMaterialParameterInfo, FMaterialParameterMetadata> GetMaterialParameters(UMaterialInterface* Material);
|
||||
using InfoMetaMap = TMap<FMaterialParameterInfo, FMaterialParameterMetadata>;
|
||||
|
||||
// Get all the material parameters of the specified material including
|
||||
// both parameters that have been overridden and those that haven't.
|
||||
//
|
||||
static InfoMetaMap GetMaterialParameters(UMaterialInterface* Material);
|
||||
|
||||
// Find a material parameter in the InfoMap.
|
||||
// If it doesn't exist, or report an error and return nullptr.
|
||||
//
|
||||
static FMaterialParameterMetadata *FindParameter(
|
||||
InfoMetaMap &Map, const FMaterialParameterInfo &ID, WingOut Errors);
|
||||
|
||||
// If the material uses custom primitive data, print an error
|
||||
// and return false.
|
||||
//
|
||||
static bool CheckNoCustomPrimitiveData(
|
||||
const FMaterialParameterInfo &ID, const FMaterialParameterMetadata &Data, WingOut Errors);
|
||||
|
||||
// Convert a parameter ID into an ID string.
|
||||
//
|
||||
static bool ParseID(const FString& Str, FMaterialParameterInfo& ID, WingOut Errors);
|
||||
|
||||
// Parse a parameter ID String into a parameter ID.
|
||||
//
|
||||
static FString StringID(const FMaterialParameterInfo& ID);
|
||||
|
||||
// Remove a material parameter override from a material instance.
|
||||
// If there is no override, print an error and return false.
|
||||
//
|
||||
static bool RemoveOverride(
|
||||
const FMaterialParameterInfo &ID, UMaterialInstanceConstant *MI, WingOut Errors);
|
||||
|
||||
// Format a material parameter value.
|
||||
//
|
||||
static FString FormatValue(const FMaterialParameterValue& Value);
|
||||
|
||||
// Print out a material parameter with ID.
|
||||
//
|
||||
static void Print(const FMaterialParameterInfo &Info, const FMaterialParameterMetadata &Meta);
|
||||
|
||||
// Print out a map full of material parameters. If Headings is
|
||||
// true, the parameters are grouped by inherited/overridden.
|
||||
//
|
||||
static void PrintAll(const InfoMetaMap &Params, bool Headings);
|
||||
|
||||
static bool ParseMaterialParameterAssociation(const FString& Str, EMaterialParameterAssociation& OutAssociation, WingOut Errors);
|
||||
static void FormatMaterialParameter(const FMaterialParameterInfo& Info, const FMaterialParameterMetadata& Meta);
|
||||
|
||||
|
||||
private:
|
||||
static void ReportBadToken(WingTokenizer &Tok, const TCHAR *Expected, WingOut Errors);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "IPropertyRowGenerator.h"
|
||||
#include "PropertyHandle.h"
|
||||
#include "WingHandler.h"
|
||||
|
||||
class FJsonObject;
|
||||
class FJsonValue;
|
||||
|
||||
// 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.
|
||||
//
|
||||
// In UE Wingman, we are encouraging the use of
|
||||
// IPropertyHandle over FProperty. The FProperty API sucks:
|
||||
// it's buggy and unreliable. Use IPropertyHandle instead.
|
||||
//
|
||||
|
||||
class WingPropHandle
|
||||
{
|
||||
public:
|
||||
using Handles = TArray<TSharedPtr<IPropertyHandle>>;
|
||||
using FlatTree = TArray<IDetailTreeNode *, TInlineAllocator<500>>;
|
||||
|
||||
WingPropHandle() {}
|
||||
|
||||
// Get all properties of a UObject. Returns the handles.
|
||||
// If RootFilter is true, only properties inside the root object are returned.
|
||||
Handles AllProperties(UObject* Obj, bool RootFilter);
|
||||
|
||||
// Get all properties of a struct. Does not copy — the data
|
||||
// pointer must remain valid for the lifetime of this object.
|
||||
// If RootFilter is true, only properties inside the root object are returned.
|
||||
Handles AllProperties(const UStruct* ScriptStruct, uint8* Data, bool RootFilter);
|
||||
|
||||
// Get a single named property from a UObject.
|
||||
// If RootFilter is true, only properties inside the root object are returned.
|
||||
TSharedPtr<IPropertyHandle> TryNamedProperty(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<IPropertyHandle> TryNamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter);
|
||||
|
||||
// Like TryNamedProperty, but prints an error if the property is not found.
|
||||
TSharedPtr<IPropertyHandle> NamedProperty(UObject* Obj, FName Name, bool RootFilter, WingOut Errors);
|
||||
TSharedPtr<IPropertyHandle> NamedProperty(const UStruct* ScriptStruct, uint8* Data, FName Name, bool RootFilter, WingOut Errors);
|
||||
|
||||
// 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, WingOut Errors);
|
||||
|
||||
// Organize properties by name. If there's more than one property
|
||||
// of a given name, print an error and returns false, and returns a map
|
||||
// that contains one of the two duplicates.
|
||||
static bool OrganizeByName(const Handles &H, TMap<FName, TSharedPtr<IPropertyHandle>> &Result, WingOut Errors);
|
||||
|
||||
// Get/set text. Compared to the built in methods for getting and setting
|
||||
// text, these support more concise enum values, FEdGraphPinType, and
|
||||
// more concise Class properties.
|
||||
static FString GetText(IPropertyHandle& Handle);
|
||||
static bool SetText(IPropertyHandle& Handle, const FString& Text, WingOut Errors);
|
||||
|
||||
// Store a Json value into a property handle. The Json value must be
|
||||
// a string, number, or boolean.
|
||||
static bool SetJson(IPropertyHandle& Handle, const TSharedPtr<FJsonValue>& JsonValue, WingOut Errors);
|
||||
|
||||
// Populate a whole bunch of properties from a Json object. If
|
||||
// AllOptional is true, the Json may supply a subset of the properties.
|
||||
// If not, the Json must supply all of them, excepting properties that
|
||||
// are explicitly marked Optional.
|
||||
static bool PopulateFromJson(TArray<TSharedPtr<IPropertyHandle>>& Props, const FJsonObject& Json, bool AllOptional, WingOut Errors);
|
||||
|
||||
// Print a single property in a standardized format:
|
||||
// editable|readonly Type Name = Value
|
||||
static void Print(IPropertyHandle& Handle, FStringBuilderBase& Out);
|
||||
|
||||
private:
|
||||
struct Root
|
||||
{
|
||||
const UStruct* Struct = nullptr;
|
||||
uint8* Base = nullptr;
|
||||
uint8* End = nullptr;
|
||||
TSharedPtr<IPropertyRowGenerator> Generator;
|
||||
};
|
||||
|
||||
static bool IsPinTypeProperty(FProperty* Prop);
|
||||
static bool IsInsideRootObject(const Root& Root, IPropertyHandle& Handle);
|
||||
TArray<Root> Roots;
|
||||
|
||||
static TSharedRef<IPropertyRowGenerator> CreateGenerator();
|
||||
Root& GetRootForObject(UObject* Obj);
|
||||
Root& GetRootForStruct(const UStruct* ScriptStruct, uint8* Data);
|
||||
static void AllTreeNodesRecursive(const TSharedRef<IDetailTreeNode>& Node, FlatTree& Out);
|
||||
static FlatTree AllTreeNodes(Root& Root);
|
||||
|
||||
Handles AllProperties(Root& Root, bool RootFilter);
|
||||
static TSharedPtr<IPropertyHandle> TryNamedProperty(Root& Root, FName Name, bool RootFilter);
|
||||
};
|
||||
@@ -12,11 +12,12 @@ struct FWingProperty
|
||||
|
||||
FProperty* Prop = nullptr;
|
||||
void* Container = nullptr;
|
||||
bool Editable = false;
|
||||
|
||||
// Construct a property reference.
|
||||
//
|
||||
FWingProperty(FProperty* InProp, void* InContainer)
|
||||
: Prop(InProp), Container(InContainer) {}
|
||||
FWingProperty(FProperty* InProp, void* InContainer, bool Edit)
|
||||
: Prop(InProp), Container(InContainer), Editable(Edit) {}
|
||||
|
||||
// Construct a null property reference.
|
||||
//
|
||||
@@ -30,8 +31,7 @@ struct FWingProperty
|
||||
// error. These will do automatic conversion of numeric
|
||||
// types to other numeric types, as long as the value
|
||||
// fits. They will also do text to any type, as long as
|
||||
// the value parses as a value of the desired type. If
|
||||
// you
|
||||
// the value parses as a value of the desired type.
|
||||
//
|
||||
bool SetObject(UObject *Obj, WingOut Errors) const;
|
||||
bool SetDouble(double D, WingOut Errors) const;
|
||||
@@ -57,6 +57,10 @@ struct FWingProperty
|
||||
//
|
||||
FString GetTruncatedText(int32 MaxLen) const;
|
||||
|
||||
// Print the property's editflag, type, name, and value.
|
||||
//
|
||||
void Print(WingOut Out) const;
|
||||
|
||||
// Get the Category metadata.
|
||||
//
|
||||
FString GetCategory() const;
|
||||
@@ -65,16 +69,22 @@ struct FWingProperty
|
||||
//
|
||||
explicit operator bool() const { return Prop != nullptr; }
|
||||
|
||||
// Get the raw properties of the specified object or struct.
|
||||
// Get all the properties of the specified object or struct.
|
||||
//
|
||||
// This gets the properties that are literally present in the
|
||||
// specified object or struct. No special interpretation is done.
|
||||
//
|
||||
static TArray<FWingProperty> GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags);
|
||||
static TArray<FWingProperty> GetAll(FWingStructAndUStruct Obj);
|
||||
|
||||
// Get all the visible properties of the specified object or struct.
|
||||
//
|
||||
// This gets the properties that have CPF_Edit marked on them.
|
||||
//
|
||||
static TArray<FWingProperty> GetVisible(FWingStructAndUStruct Obj);
|
||||
|
||||
// Get just the names of the properties of the specified struct/class.
|
||||
//
|
||||
static TArray<FName> GetNames(UStruct *US, EPropertyFlags Flags);
|
||||
static TArray<FName> GetVisibleNames(UStruct *US);
|
||||
|
||||
// Remove any properties with the specified name.
|
||||
//
|
||||
@@ -94,12 +104,10 @@ struct FWingProperty
|
||||
// click a material graph node, the details panel shows you properties
|
||||
// of the node, but also properties of the material expression.
|
||||
//
|
||||
// When editing an inherited ActorComponent, you're actually editing
|
||||
// properties that *override* the original properties of the ActorComponent.
|
||||
// The 'mutable' flag tells it whether to create overrides for these
|
||||
// properties.
|
||||
// If you do not specify mutable, then all properties will be marked
|
||||
// non-editable.
|
||||
//
|
||||
static TArray<FWingProperty> GetDetails(UObject* Obj, EPropertyFlags Flags, bool Mutable);
|
||||
static TArray<FWingProperty> GetDetails(UObject* Obj, bool Mutable);
|
||||
|
||||
// Functions to populate properties from a JSON object.
|
||||
//
|
||||
@@ -108,9 +116,26 @@ struct FWingProperty
|
||||
static bool PopulateFromJson(TArray<FWingProperty>& Props, const FJsonValue& Json,
|
||||
bool AllOptional, WingOut Errors);
|
||||
|
||||
// Functions to populate properties from a JSON object.
|
||||
//
|
||||
|
||||
private:
|
||||
static void GetAll(FWingStructAndUStruct Obj, EPropertyFlags Flags, TArray<FWingProperty> &Props);
|
||||
static void StripEditable(TArray<FWingProperty> &Props);
|
||||
static bool IsUnsigned(FNumericProperty* Prop);
|
||||
static bool IsPinTypeProperty(FProperty *Prop);
|
||||
void PrintExpectsReceived(const TCHAR *Type, WingOut Errors) const;
|
||||
bool CheckImportTextResult(const FString &Value, WingOut Errors) const;
|
||||
bool CheckEditable(WingOut Errors) const;
|
||||
|
||||
// Convert int64 to double losslessly. If it can't be done, generate an error.
|
||||
static bool LosslessInt64ToDouble(int64 I, double &D, WingOut Errors);
|
||||
|
||||
// Convert double to int64 losslessly. If it can't be done, generate an error.
|
||||
static bool LosslessDoubleToInt64(double D, int64 &I, WingOut Errors);
|
||||
|
||||
// Unlike SetDouble, this function requires that NProp is a floating point property.
|
||||
static bool SetDoubleInternal(FNumericProperty *NProp, void *Container, double D, WingOut Errors);
|
||||
|
||||
// Unlike SetInt64, this function requires that NProp is an integral property.
|
||||
static bool SetInt64Internal(FNumericProperty *NProp, void *Container, int64 I, WingOut Errors);
|
||||
};
|
||||
|
||||
@@ -19,7 +19,7 @@ class FSocket;
|
||||
* UWingServer — editor subsystem that listens on a TCP socket and dispatches
|
||||
* JSON commands to blueprint editing handlers.
|
||||
*
|
||||
* Clients connect via TCP and exchange newline-delimited JSON messages.
|
||||
* Clients connect via TCP and exchange null-delimited JSON messages.
|
||||
* Request format: {"command": "tool_name", "param1": "value1", ...}
|
||||
* Response format: raw JSON result from the handler.
|
||||
*
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
|
||||
// ----- Tool dispatch -----
|
||||
static void AddHandler(UObject* Obj, const FString& Documentation);
|
||||
static void AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, const FString& Documentation);
|
||||
static void AddHandler(UObject* Obj, const FString& Name, UObject* Config, EWingHandlerKind Kind, UClass* FactoryClass, const FString& Documentation);
|
||||
static const TArray<FWingHandlerConfig>& AllHandlers() { return GWingServer->WingHandlerRegistry; }
|
||||
|
||||
private:
|
||||
@@ -74,6 +74,8 @@ private:
|
||||
FLogCaptureOutputDevice LogCapture; // installed once at startup, enabled per-request
|
||||
TArray<FWingHandlerConfig> WingHandlerRegistry; // sorted by Name
|
||||
void BuildWingHandlerRegistry();
|
||||
void OnModulesChanged(FName ModuleName, EModuleChangeReason Reason);
|
||||
FDelegateHandle ModulesChangedHandle;
|
||||
FWingHandlerConfig* FindHandler(const FString& Name);
|
||||
|
||||
// Handle a complete JSON line and return the response JSON
|
||||
@@ -96,6 +98,14 @@ private:
|
||||
void AcceptNewConnections();
|
||||
void CleanupFinishedClients();
|
||||
static void ClientThreadFunc(UWingServer* Server, TSharedPtr<FClientConnection> Client);
|
||||
static bool ExtractRequestFromBuffer(
|
||||
TArray<uint8>& RecvBuf, int32& RecvLen, FString& OutRequest);
|
||||
static bool ReceiveMoreBytesIntoBuffer(
|
||||
FSocket* Socket, TArray<uint8>& RecvBuf, int32& RecvLen);
|
||||
static bool SendAll(FSocket* Socket, const uint8* Data, int32 BytesToSend);
|
||||
static bool ProcessRequestOnGameThread(
|
||||
const FString& Request, FString& Response);
|
||||
static void WaitForAssetRegistry();
|
||||
|
||||
// ----- Thread-safe message queue -----
|
||||
struct FPendingMessage
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "WingHandler.h"
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
//
|
||||
@@ -130,7 +130,7 @@ struct WingTokenizer
|
||||
|
||||
// Get the input text from a saved cursor position to the current
|
||||
// cursor, optionally including additional tokens.
|
||||
FStringView GetRange(FName SavePos, int32 Extra = 0) const;
|
||||
FString GetRange(FName SavePos, int32 Extra = 0) const;
|
||||
|
||||
// Tokenize a line of input. The tokens are stored in
|
||||
// the token array, and the cursor is positioned on the first
|
||||
|
||||
@@ -29,8 +29,7 @@ public:
|
||||
FName PinCategory;
|
||||
FName PinSubCategory;
|
||||
FString PinSubCategoryObject;
|
||||
// The following are only set for interfaces and Objects.
|
||||
const UClass *NativeParent = nullptr;
|
||||
FName BroadCategory;
|
||||
bool IsUserDefined = false;
|
||||
};
|
||||
|
||||
@@ -68,6 +67,8 @@ private:
|
||||
// Choose Short Name
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static FName BroadCategory(const UClass* Class);
|
||||
|
||||
// Reserve the short name for a primitive type.
|
||||
void ReserveShortName(FName Name, FName PinCategory, FName PinSubCategory);
|
||||
void ReserveShortName(FName Name);
|
||||
@@ -77,7 +78,7 @@ private:
|
||||
|
||||
// Core version: choose a short name for a path that doesn't already
|
||||
// have a short name. Records all the associated information.
|
||||
FString NewShortName(const FString &Path, FName PinCategory, const UClass *NativeParent, bool IsUserDefined);
|
||||
FString NewShortName(const FString &Path, FName PinCategory, FName BroadCategory, bool IsUserDefined);
|
||||
|
||||
// Choose a short name for an already-loaded UObject.
|
||||
FString ChooseShortName(const UObject* Obj);
|
||||
@@ -136,9 +137,10 @@ public:
|
||||
|
||||
struct Requirements
|
||||
{
|
||||
TOptional<bool> BlueprintType;
|
||||
TOptional<bool> Blueprintable;
|
||||
TOptional<bool> AllowContainer;
|
||||
bool BlueprintType = false;
|
||||
bool Blueprintable = false;
|
||||
bool AllowContainer = false;
|
||||
bool AllowNone = false;
|
||||
UClass *IsChildOf = nullptr;
|
||||
FName PinCategory = FName();
|
||||
};
|
||||
@@ -153,16 +155,6 @@ public:
|
||||
// requirements, prints an error and returns false.
|
||||
static bool TextToType(const FString& Text, FEdGraphPinType& OutPinType, const Requirements &Require, WingOut Errors);
|
||||
|
||||
// Parse a type. If it doesn't parse, or if the type doesn't satisfy the
|
||||
// requirements, prints an error and returns false. Requires that the type
|
||||
// is an object type (even if that's not specified in the requirements struct).
|
||||
static UClass* TextToOneObjectType(const FString& Text, const Requirements &Require, WingOut Errors);
|
||||
|
||||
// Parse a type. If it doesn't parse, or if the type doesn't satisfy the
|
||||
// requirements, prints an error and returns false. Requires that the type
|
||||
// is an interface type (even if that's not specified in the requirements struct).
|
||||
static UClass* TextToOneInterfaceType(const FString& Text, const Requirements &Require, WingOut Errors);
|
||||
|
||||
private:
|
||||
// ---------------------------------------------------------------------------
|
||||
// Subsystem lifecycle
|
||||
|
||||
@@ -68,29 +68,43 @@ public:
|
||||
static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; }
|
||||
static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); }
|
||||
static FName GetFName(const WingVariables::Var &Var) { return Var.Name; }
|
||||
static FName GetFName(const TSharedPtr<IPropertyHandle> &H);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// When searching an array of structs, you want to
|
||||
// return a pointer to one of the structs. When
|
||||
// searching an array of pointers, you want to return
|
||||
// one of the pointers. These helpers make it easy for
|
||||
// the search templates below to do that.
|
||||
// one of the pointers. When searching an array of
|
||||
// smart pointers, you want to return one of the smart
|
||||
// pointers. The AsPointerLike helper below makes it
|
||||
// easy for the search templates below to do that.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
template<class T> static const T* EltAsPtr(const TArray<T> &A, const T &Elt) { return &Elt; }
|
||||
template<class T> static T* EltAsPtr(TArray<T> &A, T &Elt) { return &Elt; }
|
||||
template<class T> static T* EltAsPtr(const TArray<T*> &A, T* &Elt) { return Elt; }
|
||||
template<class T> static T* EltAsPtr(TArray<T*> &A, T* &Elt) { return Elt; }
|
||||
template<class T> static const T* EltAsPtr(const TArray<const T*> &A, const T* &Elt) { return Elt; }
|
||||
template<class T> static const T* EltAsPtr(TArray<const T*> &A, const T* &Elt) { return Elt; }
|
||||
template<class T> struct IsPointerLike : std::false_type {};
|
||||
template<class T> struct IsPointerLike<T*> : std::true_type {};
|
||||
template<class T> struct IsPointerLike<TSharedPtr<T>> : std::true_type {};
|
||||
template<class T> struct IsPointerLike<TWeakObjectPtr<T>> : std::true_type {};
|
||||
template<class T> struct IsPointerLike<TObjectPtr<T>> : std::true_type {};
|
||||
template<class T> struct IsPointerLike<TStrongObjectPtr<T>> : std::true_type {};
|
||||
|
||||
template<class T>
|
||||
static decltype(auto) AsPointerLike(T&& Elt)
|
||||
{
|
||||
if constexpr (IsPointerLike<std::remove_cvref_t<T>>::value)
|
||||
{
|
||||
return std::remove_cvref_t<T>(Elt);
|
||||
}
|
||||
else
|
||||
{
|
||||
return &Elt;
|
||||
}
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Search Templates. Search an array for an item with a
|
||||
// given name. Works correctly no matter how a name is
|
||||
// given name. Works correctly no matter how a name is
|
||||
// expressed: eg, "A+B" finds the same results as
|
||||
// "A+B". Handles failure cases correctly: two
|
||||
// items with the same name, no such item, and malformed
|
||||
@@ -107,9 +121,9 @@ public:
|
||||
template<typename ArrayType>
|
||||
static auto FindOneWithInternalID(FName InternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
|
||||
{
|
||||
decltype(EltAsPtr(Array, Array[0])) Result = nullptr;
|
||||
decltype(AsPointerLike(Array[0])) Result = nullptr;
|
||||
int Count = 0;
|
||||
for (auto &Elt : Array) if (GetFName(Elt) == InternalID) { Count++; Result = EltAsPtr(Array, Elt); }
|
||||
for (auto &Elt : Array) if (GetFName(Elt) == InternalID) { Count++; Result = AsPointerLike(Elt); }
|
||||
if (!CheckExactlyOneNamed(Count, Kind, InternalID, Errors)) Result = nullptr;
|
||||
return Result;
|
||||
}
|
||||
@@ -117,28 +131,12 @@ public:
|
||||
template<typename ArrayType>
|
||||
static auto FindOneWithExternalID(const FString &ExternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
|
||||
{
|
||||
decltype(EltAsPtr(Array, Array[0])) Result = nullptr;
|
||||
decltype(AsPointerLike(Array[0])) Result = nullptr;
|
||||
FName InternalID = CheckInternalizeID(ExternalID, Errors);
|
||||
if (!InternalID.IsNone()) Result = FindOneWithInternalID(InternalID, Array, Kind, Errors);
|
||||
return Result;
|
||||
}
|
||||
|
||||
template<typename ArrayType>
|
||||
static bool FindNoneWithInternalID(FName InternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
|
||||
{
|
||||
for (auto &Elt : Array) if (GetFName(Elt) == InternalID)
|
||||
return CheckExactlyNoneNamed(1, Kind, InternalID, Errors);
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename ArrayType>
|
||||
static bool FindNoneWithExternalID(const FString &ExternalID, ArrayType &&Array, const TCHAR *Kind, WingOut Errors)
|
||||
{
|
||||
FName InternalID = CheckInternalizeID(ExternalID, Errors);
|
||||
if (InternalID.IsNone()) return false;
|
||||
return FindNoneWithInternalID(InternalID, Array, Kind, Errors);
|
||||
}
|
||||
|
||||
static bool FindNoDuplicateName(TSet<FName> &Collection, FName InternalID, const TCHAR *Kind, WingOut Errors)
|
||||
{
|
||||
if (Collection.Contains(InternalID))
|
||||
@@ -276,9 +274,6 @@ public:
|
||||
// ----- Common Error Reporting -----
|
||||
static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors);
|
||||
static bool CheckExactlyOneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors);
|
||||
static bool CheckExactlyNoneNamed(int Count, const TCHAR *Kind, const FString &Name, WingOut Errors);
|
||||
static bool CheckExactlyNoneNamed(int Count, const TCHAR *Kind, FName Name, WingOut Errors);
|
||||
|
||||
static bool CheckCanRename(UEdGraphNode* Node, const FString &Name, WingOut Errors);
|
||||
};
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ def disconnect():
|
||||
|
||||
def send_and_receive(message):
|
||||
"""Send a JSON message to the editor and return the null-terminated response."""
|
||||
data = json.dumps(message) + "\n"
|
||||
data = json.dumps(message) + "\0"
|
||||
sock.sendall(data.encode())
|
||||
|
||||
result = b""
|
||||
|
||||
@@ -41,7 +41,7 @@ def main():
|
||||
print(f"Cannot connect to {HOST}:{PORT} — is the editor running?")
|
||||
sys.exit(1)
|
||||
|
||||
sock.sendall((json.dumps(msg) + "\n").encode())
|
||||
sock.sendall((json.dumps(msg) + "\0").encode())
|
||||
|
||||
result = b""
|
||||
while True:
|
||||
|
||||
Reference in New Issue
Block a user