UE Wingman renaming complete.

This commit is contained in:
2026-03-18 10:29:38 -04:00
parent c55c5d8953
commit a2f6a21d29
134 changed files with 36 additions and 36 deletions

View File

@@ -0,0 +1,52 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "Misc/Paths.h"
#include "Misc/PackageName.h"
#include "HAL/FileManager.h"
#include "Asset_Backup.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Backup : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Asset to back up"))
FString Asset;
virtual FString GetDescription() const override
{
return TEXT("Copy an asset's .uasset file to a .uasset.bak backup.");
}
virtual void Handle() override
{
FString Filename = FPaths::ConvertRelativePathToFull(
FPackageName::LongPackageNameToFilename(Asset, FPackageName::GetAssetPackageExtension()));
if (!IFileManager::Get().FileExists(*Filename))
{
UWingServer::Printf(TEXT("ERROR: Asset file not found: %s\n"), *Filename);
return;
}
FString BackupFilename = Filename + TEXT(".bak");
uint32 CopyResult = IFileManager::Get().Copy(*BackupFilename, *Filename, true);
if (CopyResult != COPY_OK)
{
UWingServer::Printf(TEXT("ERROR: Failed to copy %s to %s\n"), *Filename, *BackupFilename);
return;
}
UWingServer::Printf(TEXT("Backed up to %s\n"), *BackupFilename);
}
};

View File

@@ -0,0 +1,127 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "Misc/PackageName.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "HAL/FileManager.h"
#include "UObject/LinkerLoad.h"
#include "UObject/Package.h"
#include "Asset_Delete.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Delete : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Asset to delete"))
FString Asset;
UPROPERTY(meta=(Optional, Description="If true, skip reference check and force delete"))
bool Force = false;
virtual FString GetDescription() const override
{
return TEXT("Delete a .uasset after verifying no references. "
"Use force=true to skip the reference check.");
}
virtual void Handle() override
{
// Verify the asset file exists on disk
FString PackageFilename = FPackageName::LongPackageNameToFilename(
Asset, FPackageName::GetAssetPackageExtension());
PackageFilename = FPaths::ConvertRelativePathToFull(PackageFilename);
if (!IFileManager::Get().FileExists(*PackageFilename))
{
UWingServer::Printf(TEXT("ERROR: Asset file not found on disk: %s\n"), *PackageFilename);
return;
}
// Check references
IAssetRegistry& Registry = *IAssetRegistry::Get();
TArray<FName> Referencers;
Registry.GetReferencers(FName(*Asset), Referencers);
// Filter out self-references
Referencers.RemoveAll([this](const FName& Ref) {
return Ref.ToString() == Asset;
});
if (Referencers.Num() > 0 && !Force)
{
UWingServer::Printf(TEXT("ERROR: Asset is still referenced by %d package(s):\n"), Referencers.Num());
for (const FName& Ref : Referencers)
{
FString RefStr = Ref.ToString();
UPackage* RefPackage = FindPackage(nullptr, *RefStr);
UWingServer::Printf(TEXT(" %s%s\n"), *RefStr,
RefPackage ? TEXT(" (loaded)") : TEXT(" (on-disk only)"));
}
UWingServer::Print(TEXT("Use force=true to skip the reference check.\n"));
return;
}
// Force delete: unload the package from memory first
if (Force && Referencers.Num() > 0)
{
UWingServer::Printf(TEXT("WARNING: Force-deleting despite %d referencer(s).\n"), Referencers.Num());
}
// Mark the package, and all the objects in it, as NOT
// GC Roots. Also, make them undiscoverable.
UPackage* Package = FindPackage(nullptr, *Asset);
if (Package)
{
// Collect all objects in this package
TArray<UObject*> ObjectsInPackage;
GetObjectsWithPackage(Package, ObjectsInPackage);
// Clear flags and remove from root to allow GC
for (UObject* Obj : ObjectsInPackage)
{
if (Obj)
{
Obj->ClearFlags(RF_Standalone | RF_Public);
Obj->RemoveFromRoot();
}
}
Package->ClearFlags(RF_Standalone | RF_Public);
Package->RemoveFromRoot();
}
// The loader that loaded the package might still
// have a file lock on it. Unlock the file.
ResetLoaders(Package);
// Delete the file on disk
bool bDeleted = IFileManager::Get().Delete(*PackageFilename, false, true);
if (!bDeleted)
{
UWingServer::Printf(TEXT("ERROR: Failed to delete file from disk: %s\n"), *PackageFilename);
return;
}
// Trigger an asset registry rescan so it notices the deletion
FString PackageDir;
int32 LastSlash;
if (Asset.FindLastChar(TEXT('/'), LastSlash))
{
PackageDir = Asset.Left(LastSlash);
Registry.ScanPathsSynchronous({PackageDir}, true);
}
UWingServer::Printf(TEXT("Deleted %s\n"), *Asset);
}
};

View File

@@ -0,0 +1,69 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "Asset_FindReferences.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_FindReferences : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Asset to find references for"))
FString Asset;
virtual FString GetDescription() const override
{
return TEXT("Find all assets that reference a given asset.");
}
virtual void Handle() override
{
IAssetRegistry& Registry = *IAssetRegistry::Get();
// Verify the asset exists
FAssetData AssetData = Registry.GetAssetByObjectPath(FSoftObjectPath(Asset));
if (!AssetData.IsValid())
{
UWingServer::Printf(TEXT("ERROR: Asset not found: %s\n"), *Asset);
return;
}
TArray<FName> Referencers;
Registry.GetReferencers(FName(*Asset), Referencers);
if (Referencers.Num() == 0)
{
UWingServer::Print(TEXT("No referencers found.\n"));
return;
}
// Classify referencers by looking up their asset class
for (const FName& Ref : Referencers)
{
FString RefStr = Ref.ToString();
TArray<FAssetData> RefAssets;
Registry.GetAssetsByPackageName(Ref, RefAssets);
if (RefAssets.Num() > 0)
{
UWingServer::Printf(TEXT("%s %s\n"),
*WingUtils::FormatName(RefAssets[0].GetClass()),
*RefStr);
}
else
{
UWingServer::Printf(TEXT("Unknown %s\n"), *RefStr);
}
}
}
};

View File

@@ -0,0 +1,71 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "AssetToolsModule.h"
#include "IAssetTools.h"
#include "Asset_Rename.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Rename : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Asset to rename"))
FString Asset;
UPROPERTY(meta=(Description="New package path or just a new name"))
FString NewPath;
virtual FString GetDescription() const override
{
return TEXT("Rename or move an asset with reference fixup.");
}
virtual void Handle() override
{
// Load the asset
WingFetcher F;
UObject* AssetObj = F.Asset(Asset).GetObj();
if (!AssetObj) return;
// Parse new path into package path and asset name
FString NewPackagePath = FPackageName::GetLongPackagePath(NewPath);
FString NewAssetName = FPackageName::GetShortName(NewPath);
if (NewPackagePath.IsEmpty())
{
// No slash — just a new name, keep the same directory
NewPackagePath = FPackageName::GetLongPackagePath(Asset);
NewAssetName = NewPath;
if (NewPackagePath.IsEmpty())
{
UWingServer::Printf(TEXT("ERROR: Cannot determine directory from Asset '%s'\n"), *Asset);
return;
}
}
// Perform the rename with reference fixup
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools");
IAssetTools& AssetTools = AssetToolsModule.Get();
TArray<FAssetRenameData> RenameData;
RenameData.Add(FAssetRenameData(AssetObj, NewPackagePath, NewAssetName));
if (!AssetTools.RenameAssets(RenameData))
{
UWingServer::Print(TEXT("ERROR: Rename failed. The target path may be invalid or a conflicting asset may exist.\n"));
return;
}
UWingServer::Printf(TEXT("Renamed to %s/%s\n"), *NewPackagePath, *NewAssetName);
}
};

View File

@@ -0,0 +1,75 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "Misc/PackageName.h"
#include "FileHelpers.h"
#include "HAL/FileManager.h"
#include "UObject/LinkerLoad.h"
#include "Asset_Restore.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Restore : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Asset to restore"))
FString Asset;
virtual FString GetDescription() const override
{
return TEXT("Restore a .uasset file from its .uasset.bak backup, reloading it in the editor.");
}
virtual void Handle() override
{
FString Filename = FPaths::ConvertRelativePathToFull(
FPackageName::LongPackageNameToFilename(Asset, FPackageName::GetAssetPackageExtension()));
FString BackupFilename = Filename + TEXT(".bak");
if (!IFileManager::Get().FileExists(*BackupFilename))
{
UWingServer::Printf(TEXT("ERROR: Backup file not found: %s\n"), *BackupFilename);
return;
}
// Release file handles if the package is loaded
UPackage* Package = FindPackage(nullptr, *Asset);
if (Package)
{
ResetLoaders(Package);
}
// Copy backup over the original
uint32 CopyResult = IFileManager::Get().Copy(*Filename, *BackupFilename, true);
if (CopyResult != COPY_OK)
{
UWingServer::Printf(TEXT("ERROR: Failed to copy backup over %s\n"), *Asset);
return;
}
// Reload the package if it was loaded
if (Package)
{
bool bReloaded = false;
FText ErrorMessage;
UEditorLoadingAndSavingUtils::ReloadPackages({Package}, bReloaded, ErrorMessage, EReloadPackagesInteractionMode::AssumePositive);
if (!bReloaded)
{
UWingServer::Printf(TEXT("WARNING: Restored %s but reload failed: %s\n"),
*Asset, *ErrorMessage.ToString());
return;
}
}
UWingServer::Printf(TEXT("Restored %s from backup\n"), *Asset);
}
};

View File

@@ -0,0 +1,96 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "Asset_Search.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Asset_Search : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(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"))
FString Type;
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50)"))
int32 Limit = 50;
virtual FString GetDescription() const override
{
return TEXT("Search for assets by name and/or type. At least one of Query or Type must be specified.");
}
virtual void Handle() override
{
if (Query.IsEmpty() && Type.IsEmpty())
{
UWingServer::Print(TEXT("ERROR: At least one of Query or Type must be specified\n"));
return;
}
// Build the asset registry filter
FARFilter Filter;
Filter.bRecursiveClasses = true;
Filter.bRecursivePaths = true;
Filter.PackagePaths.Add(FName(TEXT("/Game")));
if (!Type.IsEmpty())
{
UClass* TypeClass = WingUtils::FindClassByName(Type);
if (!TypeClass)
{
UWingServer::Printf(TEXT("ERROR: Unknown asset type '%s'\n"), *Type);
return;
}
Filter.ClassPaths.Add(TypeClass->GetClassPathName());
}
// Query the asset registry
IAssetRegistry& AR = FModuleManager::LoadModuleChecked<FAssetRegistryModule>("AssetRegistry").Get();
TArray<FAssetData> Candidates;
AR.GetAssets(Filter, Candidates);
// Filter by query substring and collect results
TArray<FAssetData> Results;
for (const FAssetData& Data : Candidates)
{
if (Results.Num() >= Limit) break;
if (!Query.IsEmpty())
{
if (!Data.AssetName.ToString().Contains(Query, ESearchCase::IgnoreCase) &&
!Data.PackageName.ToString().Contains(Query, ESearchCase::IgnoreCase))
continue;
}
Results.Add(Data);
}
for (const FAssetData& Data : Results)
{
UWingServer::Printf(TEXT("%s %s\n"),
*WingUtils::FormatName(Data.GetClass()),
*Data.PackageName.ToString());
}
if (Results.Num() == 0)
{
UWingServer::Print(TEXT("No assets found.\n"));
}
else if (Results.Num() >= Limit)
{
UWingServer::Printf(TEXT("WARNING: You reached the limit of %d, to raise it, specify the Limit parameter.\n"), Limit);
}
}
};

View File

@@ -0,0 +1,79 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingJson.h"
#include "WingProperty.h"
#include "WingBlueprintVar.h"
#include "WingUtils.h"
#include "WingTypes.h"
#include "Engine/Blueprint.h"
#include "EdGraphSchema_K2.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintVariable_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintVariable_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Name of the new variable"))
FString Name;
UPROPERTY(meta=(Optional, Description="Variable configuration: VarType, Category, DefaultValue, InstanceEditable, BlueprintReadOnly, ExposeOnSpawn, Private, ExposeToCinematics, etc."))
FWingJsonObject Config;
virtual FString GetDescription() const override
{
return TEXT("Add a new member variable to a Blueprint. Pass Config to set type, category, flags, etc.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
// Check for duplicate variable name
FName VarFName(*Name);
if (FBlueprintEditorUtils::FindNewVariableIndex(BP, VarFName) != INDEX_NONE)
{
UWingServer::Printf(TEXT("ERROR: Variable '%s' already exists in %s\n"), *Name, *WingUtils::FormatName(BP));
return;
}
// Add the variable with a default type
FEdGraphPinType DefaultType;
DefaultType.PinCategory = UEdGraphSchema_K2::PC_Int;
if (!FBlueprintEditorUtils::AddMemberVariable(BP, VarFName, DefaultType))
{
UWingServer::Printf(TEXT("ERROR: Failed to add variable '%s' to %s\n"), *Name, *WingUtils::FormatName(BP));
return;
}
// Find the newly created variable description
FBlueprintVar Editor(BP, Name);
if (Editor.NotFound()) return;
// Apply config if provided
if (Config.Json && Config.Json->Values.Num() > 0)
{
if (!Editor.ApplyJson(Config.Json.Get()))
return;
}
UWingServer::Printf(TEXT("Created variable %s (%s) in %s\n"),
*Name, *UWingTypes::TypeToText(Editor.Desc->VarType), *WingUtils::FormatName(BP));
}
};

View File

@@ -0,0 +1,49 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingBlueprintVar.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintVariable_Delete.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintVariable_Delete : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Name of the variable to delete"))
FString Variable;
virtual FString GetDescription() const override
{
return TEXT("Remove a member variable from a Blueprint.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
FBlueprintVar Editor(BP, Variable);
if (Editor.NotFound()) return;
FBlueprintEditorUtils::RemoveMemberVariable(BP, Editor.Desc->VarName);
UWingServer::Printf(TEXT("Removed variable %s from %s\n"),
*Variable, *WingUtils::FormatName(BP));
}
};

View File

@@ -0,0 +1,47 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingBlueprintVar.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintVariable_Dump.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintVariable_Dump : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Name of the variable to inspect"))
FString Variable;
virtual FString GetDescription() const override
{
return TEXT("Show all editable properties of a Blueprint variable.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
FBlueprintVar Editor(BP, Variable);
if (Editor.NotFound()) return;
UWingServer::Printf(TEXT("Variable %s in %s:\n"), *Variable, *WingUtils::FormatName(BP));
Editor.Dump();
}
};

View File

@@ -0,0 +1,62 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingJson.h"
#include "WingProperty.h"
#include "WingBlueprintVar.h"
#include "WingUtils.h"
#include "WingTypes.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintVariable_Modify.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintVariable_Modify : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Name of the variable to modify"))
FString Variable;
UPROPERTY(meta=(Description="Properties to change: VarType, Category, DefaultValue, InstanceEditable, BlueprintReadOnly, ExposeOnSpawn, Private, ExposeToCinematics, etc."))
FWingJsonObject Properties;
virtual FString GetDescription() const override
{
return TEXT("Modify properties of an existing Blueprint variable.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
FBlueprintVar Editor(BP, Variable);
if (Editor.NotFound()) return;
if (!Properties.Json || Properties.Json->Values.Num() == 0)
{
UWingServer::Print(TEXT("ERROR: No properties specified\n"));
return;
}
if (!Editor.ApplyJson(Properties.Json.Get()))
return;
UWingServer::Printf(TEXT("Modified variable %s (%s) in %s\n"),
*Variable, *UWingTypes::TypeToText(Editor.Desc->VarType), *WingUtils::FormatName(BP));
}
};

View File

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

View File

@@ -0,0 +1,51 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "Editor.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Editor_ListOpenAssets.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Editor_ListOpenAssets : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
virtual FString GetDescription() const override
{
return TEXT("List all currently open asset editors, showing which has focus and whether they have unsaved changes.");
}
virtual void Handle() override
{
UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
if (!Sub)
{
UWingServer::Print(TEXT("Error: AssetEditorSubsystem not available\n"));
return;
}
TArray<UObject*> EditedAssets = Sub->GetAllEditedAssets();
if (EditedAssets.IsEmpty())
{
UWingServer::Print(TEXT("No asset editors are open.\n"));
return;
}
for (UObject* Asset : EditedAssets)
{
bool bDirty = Asset->GetOutermost()->IsDirty();
UWingServer::Printf(TEXT(" %s%s\n"),
bDirty ? TEXT("[unsaved] ") : TEXT(""),
*Asset->GetPathName());
}
}
};

View File

@@ -0,0 +1,48 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "Editor.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Editor_OpenAsset.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Editor_OpenAsset : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Asset to open"))
FString Asset;
virtual FString GetDescription() const override
{
return TEXT("Open an asset in its editor and bring it to focus.");
}
virtual void Handle() override
{
WingFetcher F;
UObject* Obj = F.Walk(Asset).Cast<UObject>();
if (!Obj) return;
UAssetEditorSubsystem* Sub = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
if (!Sub)
{
UWingServer::Print(TEXT("Error: AssetEditorSubsystem not available\n"));
return;
}
if (Sub->OpenEditorForAsset(Obj))
UWingServer::Printf(TEXT("Opened editor for %s\n"), *Obj->GetPathName());
else
UWingServer::Printf(TEXT("Error: Could not open editor for %s\n"), *Obj->GetPathName());
}
};

View File

@@ -0,0 +1,63 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingToolMenu.h"
#include "WingServer.h"
#include "ToolMenus.h"
#include "GraphNode_ChooseMenu.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_ChooseMenu : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target node"))
FString Node;
UPROPERTY(meta=(Description="Menu item as shown by GraphNode_ShowMenu"))
FString Item;
virtual FString GetDescription() const override
{
return TEXT("Execute a context menu action on a node or pin. "
"Supports SplitStructPin, AddPin, AddArrayElementPin, etc. "
"Use GraphNode_ShowMenu to see available actions. ");
}
private:
virtual void Handle() override
{
WingFetcher F;
UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>();
if (!NodeObj) return;
FToolMenuContext Context;
TArray<FToolMenuEntry> Entries = WingToolMenu::GetMenuItems(NodeObj, Context);
for (FToolMenuEntry &Entry : Entries)
{
FString LabelText = Entry.Label.Get().ToString();
if (!LabelText.Equals(Item, ESearchCase::IgnoreCase))
continue;
if (WingToolMenu::Execute(Entry, Context))
{
UWingServer::Printf(TEXT("Executed: %s\n"), *LabelText);
}
else
{
UWingServer::Printf(TEXT("ERROR: Action '%s' cannot execute (greyed out)\n"), *LabelText);
}
return;
}
UWingServer::Printf(TEXT("ERROR: Menu item '%s' not found. Use GraphNode_ShowMenu to see available items.\n"), *Item);
}
};

View File

@@ -0,0 +1,102 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingJson.h"
#include "WingUtils.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphSchema.h"
#include "GraphNode_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
USTRUCT()
struct FSpawnNodeEntry
{
GENERATED_BODY()
UPROPERTY()
FString ActionName;
UPROPERTY()
int32 PosX = 0;
UPROPERTY()
int32 PosY = 0;
};
UCLASS()
class UWing_GraphNode_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target graph"))
FString Graph;
UPROPERTY(meta=(Description="Array of {Type, posX, posY} objects. Use GraphNode_SearchTypes to find types."))
FWingJsonArray Nodes;
virtual FString GetDescription() const override
{
return TEXT("Create nodes using the editor's action database. "
"Use GraphNode_SearchTypes to find types.");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return;
int32 SuccessCount = 0;
int32 TotalCount = Nodes.Array.Num();
for (const TSharedPtr<FJsonValue>& NodeVal : Nodes.Array)
{
FSpawnNodeEntry Entry;
if (!WingJson::PopulateFromJson(FSpawnNodeEntry::StaticStruct(), &Entry, NodeVal))
continue;
// Find the action by exact full name
TArray<TSharedPtr<FEdGraphSchemaAction>> Matches = WingUtils::SearchGraphActions(TargetGraph, Entry.ActionName, 0, /*ExactMatch=*/true);
if (Matches.Num() == 0)
{
UWingServer::Printf(TEXT("ERROR: No action found matching '%s'. Use GraphNodeSearchTypes to find available actions.\n"),
*Entry.ActionName);
continue;
}
if (Matches.Num() > 1)
{
UWingServer::Printf(TEXT("ERROR: Ambiguous: %d actions match '%s'.\n"),
Matches.Num(), *Entry.ActionName);
continue;
}
// Perform the action
FVector2D Location(Entry.PosX, Entry.PosY);
UEdGraphNode* NewNode = Matches[0]->PerformAction(TargetGraph, nullptr, Location, /*bSelectNewNode=*/false);
if (!NewNode)
{
UWingServer::Printf(TEXT("ERROR: PerformAction returned null for '%s'.\n"), *Entry.ActionName);
continue;
}
if (!NewNode->NodeGuid.IsValid())
NewNode->CreateNewGuid();
UWingServer::Printf(TEXT("Spawned: %s (%s)\n"),
*WingUtils::FormatName(NewNode), *WingUtils::FormatName(NewNode->GetClass()));
SuccessCount++;
}
UWingServer::Printf(TEXT("Spawned %d/%d nodes.\n"), SuccessCount, TotalCount);
}
};

View File

@@ -0,0 +1,68 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingServer.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "Materials/Material.h"
#include "IMaterialEditor.h"
#include "GraphNode_Delete.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_Delete : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Node to delete"))
FString Node;
virtual FString GetDescription() const override
{
return TEXT("Delete a node from a graph. "
"Cannot delete undeletable nodes (entry points, root nodes, etc).");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
if (!FoundNode) return;
UEdGraph* Graph = FoundNode->GetGraph();
FString NodeTitle = WingUtils::FormatName(FoundNode);
FString GraphName = WingUtils::FormatName(Graph);
if (!FoundNode->CanUserDeleteNode())
{
UWingServer::Printf(TEXT("ERROR: Cannot delete node '%s' in graph '%s' — it is not deletable.\n"),
*NodeTitle, *GraphName);
return;
}
if (Cast<UMaterialGraphNode>(FoundNode))
{
// Use the material editor's DeleteNodes to properly remove
// both the graph node and the underlying material expression.
IMaterialEditor* MatEditor = F.CastEditor<UMaterial, IMaterialEditor>();
if (!MatEditor) return;
MatEditor->DeleteNodes({FoundNode});
}
else
{
FoundNode->BreakAllNodeLinks();
Graph->RemoveNode(FoundNode);
}
UWingServer::Printf(TEXT("Deleted node '%s' from graph '%s'.\n"), *NodeTitle, *GraphName);
}
};

View File

@@ -0,0 +1,40 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingServer.h"
#include "WingFetcher.h"
#include "WingGraphExport.h"
#include "GraphNode_Dump.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_Dump : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target node"))
FString Node;
virtual FString GetDescription() const override
{
return TEXT("Dump a single node as readable text, including all pins and connections.");
}
private:
virtual void Handle() override
{
WingFetcher F;
UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>();
if (!NodeObj) return;
WingGraphExport Exporter(NodeObj);
UWingServer::Print(*Exporter.GetOutput());
UWingServer::Print(Exporter.GetDetails());
}
};

View File

@@ -0,0 +1,104 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "GraphNode_Duplicate.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_Duplicate : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target graph"))
FString Graph;
UPROPERTY(meta=(Description="Array of node names to duplicate (as returned by FormatName)"))
FWingJsonArray Nodes;
UPROPERTY(meta=(Optional, Description="X offset for duplicated nodes"))
int32 OffsetX = 50;
UPROPERTY(meta=(Optional, Description="Y offset for duplicated nodes"))
int32 OffsetY = 50;
virtual FString GetDescription() const override
{
return TEXT("Duplicate one or more nodes in a Blueprint graph. "
"Creates copies offset from the originals with new GUIDs. "
"Connections are not preserved on the duplicates.");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return;
if (Nodes.Array.Num() == 0)
{
UWingServer::Print(TEXT("ERROR: Nodes array is empty\n"));
return;
}
// Find all source nodes by name
TArray<UEdGraphNode*> SourceNodes;
for (const TSharedPtr<FJsonValue>& IdVal : Nodes.Array)
{
FString Name = IdVal->AsString();
UEdGraphNode* Found = nullptr;
for (UEdGraphNode* Node : TargetGraph->Nodes)
{
if (WingUtils::Identifies(Name, Node))
{
Found = Node;
break;
}
}
if (!Found)
{
UWingServer::Printf(TEXT("ERROR: Node '%s' not found in graph\n"), *Name);
continue;
}
SourceNodes.Add(Found);
}
if (SourceNodes.Num() == 0) return;
// Duplicate each node
for (UEdGraphNode* SourceNode : SourceNodes)
{
UEdGraphNode* NewNode = DuplicateObject<UEdGraphNode>(SourceNode, TargetGraph);
if (!NewNode)
{
UWingServer::Printf(TEXT("ERROR: Failed to duplicate %s\n"), *WingUtils::FormatName(SourceNode));
continue;
}
NewNode->CreateNewGuid();
NewNode->NodePosX += OffsetX;
NewNode->NodePosY += OffsetY;
for (UEdGraphPin* Pin : NewNode->Pins)
{
if (Pin)
Pin->LinkedTo.Empty();
}
TargetGraph->AddNode(NewNode, false, false);
UWingServer::Printf(TEXT("Duplicated: %s -> %s\n"), *WingUtils::FormatName(SourceNode), *WingUtils::FormatName(NewNode));
}
}
};

View File

@@ -0,0 +1,40 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "EdGraph/EdGraphNode.h"
#include "GraphNode_GetComment.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_GetComment : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target node"))
FString Node;
virtual FString GetDescription() const override
{
return TEXT("Get the comment text and bubble visibility of a node.");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
if (!FoundNode) return;
UWingServer::Printf(TEXT("Node: %s\n"), *WingUtils::FormatName(FoundNode));
UWingServer::Printf(TEXT("Comment: %s\n"), *FoundNode->NodeComment);
UWingServer::Printf(TEXT("BubbleVisible: %s\n"), FoundNode->bCommentBubbleVisible ? TEXT("true") : TEXT("false"));
}
};

View File

@@ -0,0 +1,63 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphSchema.h"
#include "GraphNode_SearchTypes.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_SearchTypes : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Search query string"))
FString Query;
UPROPERTY(meta=(Optional, Description="Maximum number of results (default 50, max 500)"))
int32 MaxResults = 50;
UPROPERTY(meta=(Description="Target graph (needed for context-sensitive results)"))
FString Graph;
virtual FString GetDescription() const override
{
return TEXT("Search the action database for node types that can be spawned in a graph. "
"Works with any graph type (Blueprint, Material, etc.). "
"Returns full action names for use with GraphNodeCreate.");
}
virtual void Handle() override
{
int32 ClampedMax = FMath::Clamp(MaxResults, 1, 500);
WingFetcher F;
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return;
TArray<TSharedPtr<FEdGraphSchemaAction>> Actions = WingUtils::SearchGraphActions(TargetGraph, Query, ClampedMax, /*ExactMatch=*/false);
for (const TSharedPtr<FEdGraphSchemaAction>& Action : Actions)
{
UWingServer::Printf(TEXT("%s\n"), *WingUtils::ActionFullName(Action));
}
if (Actions.Num() == 0)
{
UWingServer::Print(TEXT("No matching node types found.\n"));
}
else if (Actions.Num() >= ClampedMax)
{
UWingServer::Printf(TEXT("WARNING: Reached limit of %d results. Refine your query or increase MaxResults.\n"), ClampedMax);
}
}
};

View File

@@ -0,0 +1,44 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingServer.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingFunctionArgs.h"
#include "GraphNode_SetArgs.generated.h"
UCLASS()
class UWing_GraphNode_SetArgs : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Path to a graph node (function entry, function result, custom event, or tunnel)"))
FString Node;
UPROPERTY(meta=(Description="Comma-separated args, e.g. 'int x, float y'"))
FString Args;
virtual FString GetDescription() const override
{
return TEXT("Set the user-defined pins (arguments or return values) on a function entry, result, custom event, or tunnel node.");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>();
if (!NodeObj) return;
if (!WingFunctionArgs::HasArgs(NodeObj))
{
UWingServer::Printf(TEXT("ERROR: Node does not support editable args\n"));
return;
}
if (!WingFunctionArgs::SetArgs(NodeObj, Args)) return;
UWingServer::Printf(TEXT("Args set to: %s\n"), *WingFunctionArgs::GetArgs(NodeObj));
}
};

View File

@@ -0,0 +1,46 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "EdGraph/EdGraphNode.h"
#include "GraphNode_SetComment.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_SetComment : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target node"))
FString Node;
UPROPERTY(meta=(Description="Comment text to set"))
FString Comment;
virtual FString GetDescription() const override
{
return TEXT("Set a node's comment text, and make the comment visible. "
"Setting empty text will cause the comment to vanish.");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraphNode* FoundNode = F.Walk(Node).Cast<UEdGraphNode>();
if (!FoundNode) return;
FoundNode->NodeComment = Comment;
FoundNode->bCommentBubbleVisible = !Comment.IsEmpty();
FoundNode->bCommentBubblePinned = !Comment.IsEmpty();
UWingServer::Printf(TEXT("Comment set on %s\n"), *WingUtils::FormatName(FoundNode));
}
};

View File

@@ -0,0 +1,140 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingServer.h"
#include "WingFetcher.h"
#include "WingProperty.h"
#include "WingJson.h"
#include "WingUtils.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraphSchema_K2.h"
#include "MaterialGraph/MaterialGraphSchema.h"
#include "GraphNode_SetDefaults.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
USTRUCT()
struct FSetNodeDefaultEntry
{
GENERATED_BODY()
UPROPERTY()
FString Node;
UPROPERTY()
FString Name;
UPROPERTY()
FString Value;
};
UCLASS()
class UWing_GraphNode_SetDefaults : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target graph"))
FString Graph;
UPROPERTY(meta=(Description="Array of {node, name, value} objects"))
FWingJsonArray Pins;
virtual FString GetDescription() const override
{
return TEXT("Set the default value of input pins or material expression properties on nodes.");
}
// -----------------------------------------------------------------------
// K2 graphs: set pin default values.
// -----------------------------------------------------------------------
void HandleK2Entry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj, const UEdGraphSchema_K2* K2Schema)
{
WingFetcher F(GraphObj);
UEdGraphPin* Pin = F.Node(Entry.Node).Pin(Entry.Name).Cast<UEdGraphPin>();
if (!Pin) return;
UEdGraphNode* Node = Pin->GetOwningNode();
if (Pin->Direction != EGPD_Input)
{
UWingServer::Printf(TEXT("error: %s is an output pin\n"), *WingUtils::FormatName(Pin));
return;
}
Pin->Modify();
FString UseDefaultValue;
TObjectPtr<UObject> UseDefaultObject = nullptr;
FText UseDefaultText;
K2Schema->GetPinDefaultValuesFromString(Pin->PinType, Node, Entry.Value, UseDefaultValue, UseDefaultObject, UseDefaultText, false);
FString Error = K2Schema->IsPinDefaultValid(Pin, UseDefaultValue, UseDefaultObject, UseDefaultText);
if (!Error.IsEmpty())
{
UWingServer::Printf(TEXT("error: %s: %s\n"), *WingUtils::FormatName(Pin), *Error);
return;
}
UWingServer::AddTouchedObject(Node);
K2Schema->TrySetDefaultValue(*Pin, Entry.Value);
}
// -----------------------------------------------------------------------
// Material graphs: set material expression properties.
// -----------------------------------------------------------------------
void HandleMaterialEntry(const FSetNodeDefaultEntry& Entry, UEdGraph* GraphObj)
{
WingFetcher F(GraphObj);
UEdGraphNode* Node = F.Node(Entry.Node).Cast<UEdGraphNode>();
if (!Node) return;
TArray<WingProperty> All = WingProperty::GetAll(Node, CPF_Edit);
WingProperty P = WingProperty::FindOneExactMatch(All, Entry.Name);
if (!P) return;
UWingServer::AddTouchedObject(Node);
if (!P.SetText(Entry.Value))
return;
}
// -----------------------------------------------------------------------
virtual void Handle() override
{
// Fetch the graph once.
WingFetcher GraphFetcher;
UEdGraph* GraphObj = GraphFetcher.Walk(Graph).Cast<UEdGraph>();
if (!GraphObj) return;
const UEdGraphSchema* Schema = GraphObj->GetSchema();
const UEdGraphSchema_K2* K2Schema = Cast<UEdGraphSchema_K2>(Schema);
const UMaterialGraphSchema* MGSchema = Cast<UMaterialGraphSchema>(Schema);
if (!K2Schema && !MGSchema)
{
UWingServer::Printf(TEXT("error: unsupported graph schema %s\n"), *Schema->GetClass()->GetName());
return;
}
for (const TSharedPtr<FJsonValue>& PinVal : Pins.Array)
{
FSetNodeDefaultEntry Entry;
if (!WingJson::PopulateFromJson(FSetNodeDefaultEntry::StaticStruct(), &Entry, PinVal))
continue;
if (K2Schema)
HandleK2Entry(Entry, GraphObj, K2Schema);
else if (MGSchema)
HandleMaterialEntry(Entry, GraphObj);
}
UWingServer::Printf(TEXT("Done.\n"));
}
};

View File

@@ -0,0 +1,75 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingJson.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraphNode.h"
#include "GraphNode_SetPositions.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
USTRUCT()
struct FMoveNodeEntry
{
GENERATED_BODY()
UPROPERTY()
FString Node;
UPROPERTY()
int32 X = 0;
UPROPERTY()
int32 Y = 0;
};
UCLASS()
class UWing_GraphNode_SetPositions : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target graph"))
FString Graph;
UPROPERTY(meta=(Description="Array of {node, x, y} objects"))
FWingJsonArray Nodes;
virtual FString GetDescription() const override
{
return TEXT("Reposition one or more nodes in a Blueprint graph.");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return;
int32 SuccessCount = 0;
for (const TSharedPtr<FJsonValue>& NodeVal : Nodes.Array)
{
FMoveNodeEntry Entry;
if (!WingJson::PopulateFromJson(FMoveNodeEntry::StaticStruct(), &Entry, NodeVal)) continue;
WingFetcher FN(TargetGraph);
UEdGraphNode* Node = FN.Node(Entry.Node).Cast<UEdGraphNode>();
if (!Node) continue;
Node->NodePosX = Entry.X;
Node->NodePosY = Entry.Y;
SuccessCount++;
}
UWingServer::Printf(TEXT("Moved %d/%d nodes.\n"), SuccessCount, Nodes.Array.Num());
}
};

View File

@@ -0,0 +1,52 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingToolMenu.h"
#include "WingServer.h"
#include "ToolMenus.h"
#include "MaterialGraph/MaterialGraphNode.h"
#include "GraphNode_ShowMenu.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_GraphNode_ShowMenu : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target node"))
FString Node;
virtual FString GetDescription() const override
{
return TEXT("Show context menu actions available for a node and its pins.");
}
private:
virtual void Handle() override
{
WingFetcher F;
UEdGraphNode* NodeObj = F.Walk(Node).Cast<UEdGraphNode>();
if (!NodeObj) return;
if (Cast<UMaterialGraphNode>(NodeObj))
{
UWingServer::Printf(TEXT("Material graph nodes do not have usable context menus."));
return;
}
FToolMenuContext Context;
TArray<FToolMenuEntry> Entries = WingToolMenu::GetMenuItems(NodeObj, Context);
for (FToolMenuEntry &Entry : Entries)
{
FString LabelText = Entry.Label.Get().ToString();
UWingServer::Printf(TEXT("%s\n"), *LabelText);
}
if (Entries.IsEmpty()) UWingServer::Printf(TEXT("No selectable menu entries right now.\n"));
}
};

View File

@@ -0,0 +1,90 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingJson.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphPin.h"
#include "EdGraphSchema_K2.h"
#include "GraphPin_Connect.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
USTRUCT()
struct FConnectPinsEntry
{
GENERATED_BODY()
UPROPERTY()
FString SourcePin;
UPROPERTY()
FString TargetPin;
};
UCLASS()
class UWing_GraphPin_Connect : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target graph"))
FString Graph;
UPROPERTY(meta=(Description="Array of {sourcePin, targetPin} objects"))
FWingJsonArray Connections;
virtual FString GetDescription() const override
{
return TEXT("Connect pins between nodes in a graph (Blueprint or Material).");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>();
if (!G) return;
int32 SuccessCount = 0;
int32 TotalCount = Connections.Array.Num();
for (const TSharedPtr<FJsonValue>& ConnVal : Connections.Array)
{
FConnectPinsEntry Entry;
if (!WingJson::PopulateFromJson(FConnectPinsEntry::StaticStruct(), &Entry, ConnVal))
continue;
WingFetcher FS(G);
UEdGraphPin* SourcePin = FS.Walk(Entry.SourcePin).Cast<UEdGraphPin>();
if (!SourcePin) continue;
WingFetcher FT(G);
UEdGraphPin* TargetPin = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>();
if (!TargetPin) continue;
const UEdGraphSchema* Schema = G->GetSchema();
const FPinConnectionResponse Response = Schema->CanCreateConnection(SourcePin, TargetPin);
if (Response.Response == CONNECT_RESPONSE_DISALLOW)
{
UWingServer::Printf(TEXT("error: Cannot connect %s.%s to %s.%s: %s\n"),
*WingUtils::FormatName(SourcePin->GetOwningNode()), *WingUtils::FormatName(SourcePin),
*WingUtils::FormatName(TargetPin->GetOwningNode()), *WingUtils::FormatName(TargetPin),
*Response.Message.ToString());
continue;
}
Schema->TryCreateConnection(SourcePin, TargetPin);
SuccessCount++;
}
UWingServer::Printf(TEXT("Connected %d/%d pins.\n"), SuccessCount, TotalCount);
}
};

View File

@@ -0,0 +1,106 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingJson.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphPin.h"
#include "GraphPin_Disconnect.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
USTRUCT()
struct FDisconnectPinEntry
{
GENERATED_BODY()
UPROPERTY()
FString Pin;
UPROPERTY(meta=(Optional))
FString TargetPin;
};
UCLASS()
class UWing_GraphPin_Disconnect : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target graph"))
FString Graph;
UPROPERTY(meta=(Description="Array of {pin, targetPin?} objects. If targetPin is omitted, all connections on the pin are broken."))
FWingJsonArray Disconnections;
virtual FString GetDescription() const override
{
return TEXT("Disconnect pins in a graph (Blueprint or Material). "
"Can disconnect a specific link or all links on a pin.");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>();
if (!G) return;
int32 SuccessCount = 0;
int32 TotalDisconnected = 0;
for (const TSharedPtr<FJsonValue>& DiscVal : Disconnections.Array)
{
FDisconnectPinEntry Entry;
if (!WingJson::PopulateFromJson(FDisconnectPinEntry::StaticStruct(), &Entry, DiscVal)) continue;
WingFetcher FP(G);
UEdGraphPin* Pin = FP.Walk(Entry.Pin).Cast<UEdGraphPin>();
if (!Pin) continue;
int32 DisconnectedCount = 0;
if (!Entry.TargetPin.IsEmpty())
{
WingFetcher FT(G);
UEdGraphPin* Target = FT.Walk(Entry.TargetPin).Cast<UEdGraphPin>();
if (!Target) continue;
if (!Pin->LinkedTo.Contains(Target))
{
UWingServer::Printf(TEXT("Error: %s.%s is not connected to %s.%s\n"),
*WingUtils::FormatName(Pin->GetOwningNode()), *WingUtils::FormatName(Pin),
*WingUtils::FormatName(Target->GetOwningNode()), *WingUtils::FormatName(Target));
continue;
}
Pin->BreakLinkTo(Target);
DisconnectedCount = 1;
}
else
{
DisconnectedCount = Pin->LinkedTo.Num();
if (DisconnectedCount > 0)
{
Pin->BreakAllPinLinks(true);
}
}
UWingServer::Printf(TEXT("Disconnected %d link(s) from %s.%s\n"),
DisconnectedCount,
*WingUtils::FormatName(Pin->GetOwningNode()), *WingUtils::FormatName(Pin));
SuccessCount++;
TotalDisconnected += DisconnectedCount;
}
UWingServer::Printf(TEXT("Done: %d/%d succeeded, %d links broken.\n"),
SuccessCount, Disconnections.Array.Num(), TotalDisconnected);
}
};

View File

@@ -0,0 +1,54 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingServer.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingGraphExport.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "Materials/Material.h"
#include "MaterialGraph/MaterialGraph.h"
#include "Graph_Dump.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Graph_Dump : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Path to graph"))
FString Graph;
UPROPERTY(meta=(Optional, Description="True to include less-significant details"))
bool bDetails;
virtual FString GetDescription() const override
{
return TEXT("Dump blueprint or material graphs as readable text. ");
}
virtual void Handle() override
{
WingFetcher F;
UEdGraph *G = F.Walk(Graph).Cast<UEdGraph>();
if (!G) return;
WingGraphExport Exporter(G);
UWingServer::Print(*Exporter.GetOutput());
if (bDetails)
{
UWingServer::Print(Exporter.GetDetails());
}
else
{
UWingServer::Printf(TEXT("Note: use bDetails=true to see suppressed details."));
}
}
};

View File

@@ -0,0 +1,80 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingMaterialParameter.h"
#include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h"
#include "MaterialInstance_ClearParameter.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_MaterialInstance_ClearParameter : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target material instance"))
FString MaterialInstance;
UPROPERTY(meta=(Description="Parameter name to clear"))
FString Parameter;
UPROPERTY(meta=(Description="Parameter association: 'Global', 'Layer', or 'Blend'. Default: 'Global'", Optional))
FString ParameterAssociation = TEXT("Global");
UPROPERTY(meta=(Description="Layer/blend index (0-based). Only used when ParameterAssociation is 'Layer' or 'Blend'", Optional))
int32 ParameterLayer = INDEX_NONE;
virtual FString GetDescription() const override
{
return TEXT("Remove a parameter override from a Material Instance, reverting it to the parent material's value.");
}
virtual void Handle() override
{
WingFetcher F;
UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>();
if (!MI) return;
// Parse the association string.
EMaterialParameterAssociation Association;
if (!WingMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association))
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)
{
UWingServer::Printf(TEXT("No override found for parameter '%s' (association=%s layer=%d) on %s"),
*Parameter, *ParameterAssociation, ParameterLayer, *WingUtils::FormatName(MI));
return;
}
WingUtils::SaveGenericPackage(MI);
UWingServer::Printf(TEXT("Cleared override for '%s' on %s\n"),
*Parameter, *WingUtils::FormatName(MI));
}
};

View File

@@ -0,0 +1,72 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Materials/Material.h"
#include "Materials/MaterialInterface.h"
#include "Materials/MaterialInstanceConstant.h"
#include "Factories/MaterialInstanceConstantFactoryNew.h"
#include "WingPackageMaker.h"
#include "MaterialInstance_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_MaterialInstance_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new Material Instance (e.g. '/Game/Materials/MI_GoldShiny')"))
FString AssetPath;
UPROPERTY(meta=(Description="Parent material package path (Material or Material Instance)"))
FString ParentMaterial;
virtual FString GetDescription() const override
{
return TEXT("Create a new Material Instance Constant asset with a specified parent material.");
}
virtual void Handle() override
{
WingPackageMaker Maker(AssetPath);
if (!Maker.Ok()) return;
// Load parent material by package path.
WingFetcher F;
UObject* ParentObj = F.Asset(ParentMaterial).GetObj();
UMaterialInterface* ParentMaterialObj = ParentObj ? Cast<UMaterialInterface>(ParentObj) : nullptr;
if (!ParentMaterialObj)
{
UWingServer::Printf(TEXT("ERROR: Parent material '%s' not found or not a material\n"), *ParentMaterial);
return;
}
// Create via factory + AssetTools.
UMaterialInstanceConstant* MI = Maker.CreateAsset<UMaterialInstanceConstant, UMaterialInstanceConstantFactoryNew>();
if (!MI) return;
// Set parent.
MI->Parent = ParentMaterialObj;
// Save.
bool bSaved = WingUtils::SaveGenericPackage(MI);
UWingServer::Printf(TEXT("Created %s\n"), *MI->GetPathName());
if (UMaterialInstance* ParentMI = Cast<UMaterialInstance>(ParentMaterialObj))
UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMI));
else if (UMaterial* ParentMat = Cast<UMaterial>(ParentMaterialObj))
UWingServer::Printf(TEXT("Parent: %s\n"), *WingUtils::FormatName(ParentMat));
else
UWingServer::Printf(TEXT("Parent: %s\n"), *ParentMaterialObj->GetPathName());
if (!bSaved)
UWingServer::Print(TEXT("WARNING: Package save failed\n"));
}
};

View File

@@ -0,0 +1,58 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingMaterialParameter.h"
#include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h"
#include "MaterialInstance_DumpParameters.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_MaterialInstance_DumpParameters : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target material instance"))
FString MaterialInstance;
virtual FString GetDescription() const override
{
return TEXT("List all parameters on a Material Instance, showing current values and which are overridden.");
}
virtual void Handle() override
{
WingFetcher F;
UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>();
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) { UWingServer::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) { UWingServer::Print(TEXT("\nInherited Parameters (not overridden):\n")); bHasInherited = true; }
WingMaterialParameter::FormatMaterialParameter(Info, Meta);
}
}
};

View File

@@ -0,0 +1,109 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingMaterialParameter.h"
#include "Materials/MaterialInstanceConstant.h"
#include "MaterialTypes.h"
#include "Misc/DefaultValueHelper.h"
#include "MaterialInstance_SetParameter.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_MaterialInstance_SetParameter : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target material instance"))
FString MaterialInstance;
UPROPERTY(meta=(Description="Parameter name to set"))
FString Parameter;
UPROPERTY(meta=(Description="Parameter association: 'Global', 'Layer', or 'Blend'. Default: 'Global'", Optional))
FString ParameterAssociation = TEXT("Global");
UPROPERTY(meta=(Description="Layer/blend index (0-based). Only used when ParameterAssociation is 'Layer' or 'Blend'", Optional))
int32 ParameterLayer = INDEX_NONE;
UPROPERTY(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;
virtual FString GetDescription() const override
{
return TEXT("Set a parameter override on a Material Instance.");
}
virtual void Handle() override
{
WingFetcher F;
UMaterialInstanceConstant* MI = F.Asset(MaterialInstance).Cast<UMaterialInstanceConstant>();
if (!MI) return;
// Parse the association string.
EMaterialParameterAssociation Association;
if (!WingMaterialParameter::ParseMaterialParameterAssociation(ParameterAssociation, Association))
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)
{
UWingServer::Printf(TEXT("No parameter named '%s' with association=%s layer=%d"),
*Parameter, *ParameterAssociation, ParameterLayer);
return;
}
if (Found->PrimitiveDataIndex != INDEX_NONE)
{
UWingServer::Printf(TEXT("Parameter '%s' uses custom primitive data and cannot be set on a material instance"), *Parameter);
return;
}
EMaterialParameterType Type = Found->Value.Type;
switch (Type)
{
case EMaterialParameterType::Scalar:
{
float ScalarValue;
if (!FDefaultValueHelper::ParseFloat(Value, ScalarValue))
{
UWingServer::Printf(TEXT("Failed to parse '%s' as a float"), *Value);
return;
}
MI->SetScalarParameterValueEditorOnly(ParamID, ScalarValue);
break;
}
case EMaterialParameterType::Vector:
{
FLinearColor Color;
if (!Color.InitFromString(Value))
{
UWingServer::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);
break;
}
default:
UWingServer::Printf(TEXT("Parameters of type %d (see EMaterialParameterType) are not implemented"), (int)Type);
return;
}
WingUtils::SaveGenericPackage(MI);
UWingServer::Printf(TEXT("Set '%s' = %s on %s\n"),
*Parameter, *Value, *WingUtils::FormatName(MI));
}
};

View File

@@ -0,0 +1,62 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Materials/Material.h"
#include "Material_Compile.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Material_Compile : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Material name or package path"))
FString Material;
virtual FString GetDescription() const override
{
return TEXT("Force recompile a material and check for compilation errors.");
}
virtual void Handle() override
{
// Load material
WingFetcher F;
UMaterial* MaterialObj = F.Asset(Material).Cast<UMaterial>();
if (!MaterialObj) return;
// Force recompile
MaterialObj->ForceRecompileForRendering();
// Wait for compilation to finish, then check for errors
FMaterialResource* Resource = MaterialObj->GetMaterialResource(GMaxRHIFeatureLevel);
TArray<FString> Errors;
if (Resource)
{
Resource->FinishCompilation();
Errors = Resource->GetCompileErrors();
}
if (Errors.IsEmpty())
{
UWingServer::Printf(TEXT("%s compiled successfully.\n"), *WingUtils::FormatName(MaterialObj));
}
else
{
UWingServer::Printf(TEXT("%s compiled with %d error(s):\n"), *WingUtils::FormatName(MaterialObj), Errors.Num());
for (const FString& Err : Errors)
{
UWingServer::Printf(TEXT(" %s\n"), *Err);
}
}
}
};

View File

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

View File

@@ -0,0 +1,45 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingServer.h"
#include "WingUtils.h"
#include "WingMaterialParameter.h"
#include "MaterialTypes.h"
#include "Material_DumpParameters.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Material_DumpParameters : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Material path"))
FString Material;
virtual FString GetDescription() const override
{
return TEXT("List all parameters on a Material, showing their default values.");
}
virtual void Handle() override
{
WingFetcher F;
UMaterial* Mat = F.Asset(Material).Cast<UMaterial>();
if (!Mat) return;
auto AllParams = WingMaterialParameter::GetMaterialParameters(Mat);
for (auto& [Info, Meta] : AllParams)
{
WingMaterialParameter::FormatMaterialParameter(Info, Meta);
}
if (AllParams.IsEmpty()) UWingServer::Printf(TEXT("No material parameters.\n"));
}
};

View File

@@ -0,0 +1,98 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingProperty.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "Property_Dump.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Property_Dump : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target object"))
FString Object;
UPROPERTY(meta=(Optional, Description="Substring filter for property names"))
FString Query;
UPROPERTY(meta=(Optional, Description="Truncate values to 80 characters (default true)"))
bool Truncate = true;
UPROPERTY(meta=(Optional, Description="Only show properties declared on the object's own class, not inherited ones"))
bool Local = false;
virtual FString GetDescription() const override
{
return TEXT("List all blueprint-visible properties, showing current values and which are editable.");
}
virtual void Handle() override
{
// Resolve the path to an object and get its editable template.
WingFetcher F;
UObject* Template = F.Walk(Object).Cast<UObject>();
if (!Template) return;
TArray<WingProperty> AllProps = WingProperty::GetAll(Template, CPF_Edit);
TArray<WingProperty> Props = WingProperty::FindAllSubstring(AllProps, Query);
if (Local)
{
UClass* ObjClass = Template->GetClass();
Props.RemoveAll([ObjClass](const WingProperty& P) { return P->GetOwnerStruct() != ObjClass; });
}
// Group properties by category.
TMap<FString, TArray<WingProperty>> ByCategory;
for (WingProperty& P : Props)
{
FString Category = P->HasMetaData(TEXT("Category")) ? P->GetMetaData(TEXT("Category")) : FString();
ByCategory.FindOrAdd(Category).Add(P);
}
// Sort category names, putting empty category last.
TArray<FString> Categories;
ByCategory.GetKeys(Categories);
Categories.Sort([](const FString& A, const FString& B) {
if (A.IsEmpty()) return false;
if (B.IsEmpty()) return true;
return A < B;
});
for (const FString& Category : Categories)
{
if (Category.IsEmpty())
UWingServer::Print(TEXT("\nUncategorized:\n"));
else
UWingServer::Printf(TEXT("\n%s:\n"), *Category);
for (WingProperty& P : ByCategory[Category])
{
FString PropName = WingUtils::FormatName(P.Prop);
FString ValueStr = P.GetText();
if (Truncate && (ValueStr.Len() > 80))
ValueStr = ValueStr.Left(80) + TEXT("...");
bool bEditable = !P->HasAnyPropertyFlags(CPF_EditConst);
UWingServer::Printf(TEXT(" %s %s %s = %s\n"),
bEditable ? TEXT("editable") : TEXT("readonly"),
*UWingTypes::TypeToText(P.Prop),
*PropName,
*ValueStr);
}
}
if (Props.IsEmpty())
UWingServer::Print(TEXT(" (no blueprint-visible properties found)\n"));
}
};

View File

@@ -0,0 +1,46 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingProperty.h"
#include "WingUtils.h"
#include "Property_Get.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Property_Get : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target object"))
FString Object;
UPROPERTY(meta=(Description="Property name"))
FString Property;
virtual FString GetDescription() const override
{
return TEXT("Get the value of a single property.");
}
virtual void Handle() override
{
WingFetcher F;
UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!Obj) return;
TArray<WingProperty> All = WingProperty::GetAll(Obj, CPF_Edit);
WingProperty P = WingProperty::FindOneExactMatch(All, Property);
if (!P) return;
UWingServer::Print(P.GetText());
UWingServer::Print(TEXT("\n"));
}
};

View File

@@ -0,0 +1,79 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingProperty.h"
#include "WingUtils.h"
#include "Property_Set.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Property_Set : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Target object"))
FString Object;
UPROPERTY(meta=(Description="Object mapping property names to new values in Unreal text format"))
FWingJsonObject Properties;
virtual FString GetDescription() const override
{
return TEXT("Set one or more editable properties. Values use Unreal text format.");
}
virtual void Handle() override
{
// Resolve the path to an object and get its editable template.
WingFetcher F;
UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!Obj) return;
if (!Properties.Json || Properties.Json->Values.Num() == 0)
{
UWingServer::Print(TEXT("Error: No properties specified\n"));
return;
}
// Validation pass — resolve all properties and values before modifying anything.
TArray<WingProperty> All = WingProperty::GetAll(Obj, CPF_Edit);
TArray<TPair<WingProperty, FString>> Resolved;
for (const auto& Pair : Properties.Json->Values)
{
WingProperty P = WingProperty::FindOneExactMatch(All, Pair.Key);
if (!P) return;
FString ValueStr;
if (!Pair.Value->TryGetString(ValueStr))
{
UWingServer::Printf(TEXT("Error: Value for '%s' must be a string\n"), *Pair.Key);
return;
}
Resolved.Emplace(P, ValueStr);
}
// Apply all changes.
int32 SuccessCount = 0;
for (auto& [P, ValueStr] : Resolved)
{
if (!P.SetText(ValueStr))
continue;
SuccessCount++;
}
// Save.
bool bSaved = WingUtils::SaveGenericPackage(Obj);
UWingServer::Printf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num());
if (!bSaved)
UWingServer::Print(TEXT("Warning: Save failed\n"));
}
};

View File

@@ -0,0 +1,85 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingServer.h"
#include "WingTypes.h"
#include "WingJson.h"
#include "WingUtils.h"
#include "ShowCommands.generated.h"
UCLASS()
class UWing_ShowCommands : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Substring filter for command names"))
FString Query;
UPROPERTY(meta=(Optional, Description="If true, return full details including parameter types and descriptions"))
bool Verbose = false;
virtual FString GetDescription() const override
{
return TEXT("List all available commands with their descriptions.");
}
void EmitCommand(UClass* Class)
{
if (Verbose)
{
WingUtils::FormatCommandHelp(Class);
return;
}
UWingServer::Print(WingUtils::GetHandlerName(Class));
UWingServer::Print(TEXT("("));
bool bFirst = true;
for (TFieldIterator<FProperty> PropIt(Class, EFieldIterationFlags::None); PropIt; ++PropIt)
{
if (!bFirst) UWingServer::Print(TEXT(","));
bFirst = false;
if (PropIt->HasMetaData(TEXT("Optional"))) UWingServer::Print(TEXT("?"));
UWingServer::Print(PropIt->GetName());
}
UWingServer::Print(TEXT(")\n"));
}
void EmitCommandList(bool bHalfBaked)
{
FString QueryLower = Query.ToLower();
FString PrevGroup;
for (UClass* Class : WingUtils::CollectHandlerClasses())
{
bool bIsHalfBaked = Class->GetMetaData(TEXT("ModuleRelativePath")).StartsWith(TEXT("HalfBaked/"));
if (bIsHalfBaked != bHalfBaked)
continue;
FString ToolName = WingUtils::GetHandlerName(Class);
if (!ToolName.ToLower().Contains(QueryLower))
continue;
// Blank line between groups
FString Group = WingUtils::GetHandlerGroup(Class);
if (Group != PrevGroup)
{
if (!PrevGroup.IsEmpty())
UWingServer::Print(TEXT("\n"));
PrevGroup = Group;
}
EmitCommand(Class);
}
}
virtual void Handle() override
{
UWingServer::Printf(TEXT("\n"));
EmitCommandList(false);
// UWingServer::Print(TEXT("\n--- Half-Baked (may have issues) ---\n\n"));
// EmitCommandList(true);
UWingServer::Printf(TEXT("\n"));
}
};

View File

@@ -0,0 +1,104 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "UObject/UObjectIterator.h"
#include "StructUtils/UserDefinedStruct.h"
#include "Engine/UserDefinedEnum.h"
#include "Engine/Blueprint.h"
#include "TypeName_Search.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_TypeName_Search : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Substring filter for type names"))
FString Query;
UPROPERTY(meta=(Optional, Description="Maximum number of results"))
int32 Limit = 100;
virtual FString GetDescription() const override
{
return TEXT("Search for type names usable in pin type specifications. "
"Returns short names that can be used with commands like Blueprint_ChangeVariableType.");
}
void TryMatchObject(TSet<UObject*> &Matches, UObject *Obj)
{
if (!Obj) return;
FString Name = Obj->GetName();
if (!Name.Contains(Query, ESearchCase::IgnoreCase)) return;
Matches.Add(Obj);
}
void TryMatchObjects(TSet<UObject*> &Matches, UClass *Class)
{
ForEachObjectOfClass(Class, [&](UObject *Obj){
if (Matches.Num() == Limit) return;
TryMatchObject(Matches, Obj);
}, true);
}
void TryMatchAssets(TSet<UObject*> &Matches, UClass *Class)
{
IAssetRegistry& Registry = *IAssetRegistry::Get();
TArray<FAssetData> AssetResults;
Registry.GetAssetsByClass(Class->GetClassPathName(), AssetResults, true);
for (const FAssetData& Data : AssetResults)
{
if (Matches.Num() == Limit) return;
FString Name = Data.AssetName.ToString();
if (!Name.Contains(Query, ESearchCase::IgnoreCase)) continue;
UObject *Obj = Data.GetAsset();
if (UBlueprint* BP = Cast<UBlueprint>(Obj))
Obj = BP->GeneratedClass;
TryMatchObject(Matches, Obj);
}
}
virtual void Handle() override
{
TSet<UObject*> Matches;
TryMatchObjects(Matches, UScriptStruct::StaticClass());
TryMatchObjects(Matches, UClass::StaticClass());
TryMatchObjects(Matches, UEnum::StaticClass());
TryMatchAssets(Matches, UBlueprint::StaticClass());
TryMatchAssets(Matches, UUserDefinedStruct::StaticClass());
TryMatchAssets(Matches, UUserDefinedEnum::StaticClass());
TArray<FString> Results;
for (const UObject *Obj : Matches)
{
const TCHAR *Kind = TEXT("Unknown");
if (Cast<UEnum>(Obj))
Kind = TEXT("Enum");
else if (Cast<UScriptStruct>(Obj))
Kind = TEXT("Struct");
else if (const UClass* Class = Cast<UClass>(Obj))
Kind = Class->IsChildOf(UInterface::StaticClass()) ? TEXT("Interface") : TEXT("Class");
Results.Add(FString::Printf(TEXT("%s %s\n"), Kind, *UWingTypes::TypeToText(Obj)));
}
Results.Sort();
for (const auto &Result : Results)
{
UWingServer::Print(Result);
}
if (Results.Num() == Limit)
{
UWingServer::Printf(TEXT("Search limit reached, raise it with Limit=\n"));
}
}
};

View File

@@ -0,0 +1,90 @@
#pragma once
#include "CoreMinimal.h"
#include "WingHandler.h"
#include "WingServer.h"
#include "UserManual.generated.h"
UCLASS()
class UWing_UserManual : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
virtual FString GetDescription() const override
{
return TEXT("Print the user manual.");
}
virtual void Handle() override
{
UWingServer::Print(TEXT(
"\n PATHS:"
"\n"
"\n Most commands require you to specify a path. A path starts"
"\n with an asset name, followed by comma-separated steps that"
"\n navigate into the asset. Example:"
"\n"
"\n /Game/Widgets/WB_Hotkeys,graph:EventGraph,node:Self03,pin:Result"
"\n"
"\n The navigation steps supported are:"
"\n"
"\n graph — move from a blueprint or material to a graph."
"\n node — move from a graph to a graph node"
"\n pin — move from a graph node to a pin"
"\n component — move from a blueprint to a component"
"\n levelblueprint — move from a world to a blueprint"
"\n"
"\n Steps do not always require a parameter. For example, materials"
"\n only have one graph, so you can just say:"
"\n"
"\n /Game/Materials/MyMaterial,graph"
"\n"
"\n TYPES:"
"\n"
"\n To change variable types, or function prototypes, you will"
"\n use our syntax for types. Here are some simple examples:"
"\n"
"\n boolean, int64, double, string, etc"
"\n vector, rotator, hitresult, etc"
"\n actor, character, playercontroller, etc"
"\n eblendmode, emovementmode, etc"
"\n"
"\n Notice that it's 'actor', not 'AActor'."
"\n You can use the following notations for complex types:"
"\n"
"\n soft<abp_manny>, class<pawn>, softclass<pawn>"
"\n array<int>, set<string>, map<int, string>"
"\n"
"\n FUNCTION ARGUMENTS AND RETURN VALUES:"
"\n"
"\n Function argument lists are expressed as comma-separated"
"\n lists containing type and variable name:"
"\n"
"\n double D, PlayerController P, array<int> A"
"\n"
"\n To change the arguments or return values of a function, edit the"
"\n entry or exit node of the graph using GraphNode_SetArgs."
"\n You can view the arguments using GraphNode_Dump. If a return "
"\n node doesn't exist, you may have to create it using GraphNode_Create"
"\n before you can set return values. Custom event nodes also have"
"\n editable arguments."
"\n"
"\n MATERIAL EDITING:"
"\n"
"\n We do not expose material expressions directly. Instead, you"
"\n will be editing the material graph. However, if you Graph_Dump"
"\n a material graph, you will see that the nodes contain mxprop"
"\n properties, which actually come from the material expressions."
"\n You can edit these using Property_Set on the node."
"\n"
"\n COMMANDS YOU SHOULD KNOW ABOUT AND REMEMBER:"
"\n"
"\n UserManual: this explanation"
"\n ShowCommands: a full list of all the commands"
"\n Graph_Dump: a detailed listing of any UEdGraph"
"\n Property_Dump: show information on many objects"
"\n"
));
}
};