Halfway through with repair of right-click menus

This commit is contained in:
2026-03-20 03:16:50 -04:00
parent 77a3c552f8
commit acc5bafe34
24 changed files with 228 additions and 65 deletions

View File

@@ -1,106 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingPackageMaker.h"
#include "Animation/AnimBlueprint.h"
#include "Animation/AnimBlueprintGeneratedClass.h"
#include "Animation/AnimInstance.h"
#include "Animation/Skeleton.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "AnimBlueprint_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_AnimBlueprint_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new Animation Blueprint (e.g. '/Game/AnimBP/ABP_Character')"))
FString AssetPath;
UPROPERTY(meta=(Description="Skeleton asset package path"))
FString Skeleton;
UPROPERTY(meta=(Optional, Description="Parent class name (default: AnimInstance)"))
FString ParentClass;
virtual FString GetDescription() const override
{
return TEXT("Create a new Animation Blueprint asset with a specified skeleton.");
}
virtual void Handle() override
{
WingPackageMaker Maker(AssetPath);
if (!Maker.Ok()) return;
// Resolve skeleton
WingFetcher SkeletonFetcher;
USkeleton* SkeletonObj = SkeletonFetcher.Asset(Skeleton).Cast<USkeleton>();
if (!SkeletonObj) return;
// Resolve parent class (default: UAnimInstance)
UClass* ParentClassObj = UAnimInstance::StaticClass();
if (!ParentClass.IsEmpty() && !ParentClass.Equals(TEXT("AnimInstance"), ESearchCase::IgnoreCase))
{
UClass* Found = nullptr;
for (TObjectIterator<UClass> It; It; ++It)
{
if (It->IsChildOf(UAnimInstance::StaticClass()) && WingUtils::Identifies(ParentClass, *It))
{
Found = *It;
break;
}
}
if (!Found)
{
UWingServer::Printf(TEXT("ERROR: Parent class '%s' not found (must derive from AnimInstance)\n"), *ParentClass);
return;
}
ParentClassObj = Found;
}
// Create the package and Animation Blueprint
if (!Maker.Make()) return;
UAnimBlueprint* NewAnimBP = CastChecked<UAnimBlueprint>(
FKismetEditorUtilities::CreateBlueprint(
ParentClassObj,
Maker.Package(),
FName(*Maker.Name()),
BPTYPE_Normal,
UAnimBlueprint::StaticClass(),
UAnimBlueprintGeneratedClass::StaticClass()
));
if (!NewAnimBP)
{
UWingServer::Print(TEXT("ERROR: FKismetEditorUtilities::CreateBlueprint returned null\n"));
return;
}
// Set target skeleton
NewAnimBP->TargetSkeleton = SkeletonObj;
// Compile
FKismetEditorUtilities::CompileBlueprint(NewAnimBP);
UWingServer::Printf(TEXT("Created: %s\n"), *AssetPath);
UWingServer::Printf(TEXT("ParentClass: %s\n"), *WingUtils::FormatName(ParentClassObj));
TArray<UEdGraph*> Graphs = WingUtils::AllGraphs(NewAnimBP);
for (UEdGraph* Graph : Graphs)
{
UWingServer::Printf(TEXT("Graph: %s\n"), *WingUtils::FormatName(Graph));
}
}
};

View File

@@ -1,131 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingJson.h"
#include "WingUtils.h"
#include "Animation/AnimSequence.h"
#include "Animation/BlendSpace.h"
#include "AnimBlueprint_SetBlendSpaceSamples.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
USTRUCT()
struct FBlendSpaceSampleEntry
{
GENERATED_BODY()
UPROPERTY()
FString AnimationAsset;
UPROPERTY()
float X = 0.0f;
UPROPERTY()
float Y = 0.0f;
};
UCLASS()
class UWing_AnimBlueprint_SetBlendSpaceSamples : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blend Space package path"))
FString BlendSpace;
UPROPERTY(meta=(Optional, Description="Display name for the X axis"))
FString AxisXName;
UPROPERTY(meta=(Optional, Description="Display name for the Y axis"))
FString AxisYName;
UPROPERTY(meta=(Optional, Description="Minimum value for X axis"))
float AxisXMin = 0.0f;
UPROPERTY(meta=(Optional, Description="Maximum value for X axis"))
float AxisXMax = 0.0f;
UPROPERTY(meta=(Optional, Description="Minimum value for Y axis"))
float AxisYMin = 0.0f;
UPROPERTY(meta=(Optional, Description="Maximum value for Y axis"))
float AxisYMax = 0.0f;
UPROPERTY(meta=(Optional, Description="Array of sample points, each with animationAsset, x, y"))
FWingJsonArray Samples;
virtual FString GetDescription() const override
{
return TEXT("Set axis parameters and animation sample points on a Blend Space. "
"Replaces all existing samples.");
}
virtual void Handle() override
{
// Load the blend space
WingFetcher F;
UBlendSpace* BS = F.Asset(BlendSpace).Cast<UBlendSpace>();
if (!BS) return;
// Set axis parameters
const FBlendParameter& ParamX = BS->GetBlendParameter(0);
const FBlendParameter& ParamY = BS->GetBlendParameter(1);
// We need to modify BlendParameters directly — use const_cast since there's no setter API
FBlendParameter& MutableParamX = const_cast<FBlendParameter&>(ParamX);
FBlendParameter& MutableParamY = const_cast<FBlendParameter&>(ParamY);
if (!AxisXName.IsEmpty()) MutableParamX.DisplayName = AxisXName;
if (AxisXMin != 0.0f) MutableParamX.Min = AxisXMin;
if (AxisXMax != 0.0f) MutableParamX.Max = AxisXMax;
if (!AxisYName.IsEmpty()) MutableParamY.DisplayName = AxisYName;
if (AxisYMin != 0.0f) MutableParamY.Min = AxisYMin;
if (AxisYMax != 0.0f) MutableParamY.Max = AxisYMax;
// Clear existing samples (delete from end to start)
int32 NumExisting = BS->GetNumberOfBlendSamples();
for (int32 i = NumExisting - 1; i >= 0; --i)
{
BS->DeleteSample(i);
}
// Add new samples
int32 SamplesSet = 0;
for (const TSharedPtr<FJsonValue>& SampleVal : Samples.Array)
{
FBlendSpaceSampleEntry Entry;
if (!WingJson::PopulateFromJson(FBlendSpaceSampleEntry::StaticStruct(), &Entry, SampleVal)) return;
UAnimSequence* AnimSeq = nullptr;
if (!Entry.AnimationAsset.IsEmpty())
{
WingFetcher F2;
AnimSeq = F2.Asset(Entry.AnimationAsset).Cast<UAnimSequence>();
if (!AnimSeq) return;
}
FVector SampleValue(Entry.X, Entry.Y, 0.0f);
if (AnimSeq)
{
BS->AddSample(AnimSeq, SampleValue);
}
else
{
BS->AddSample(SampleValue);
}
SamplesSet++;
}
BS->ValidateSampleData();
UWingServer::Printf(TEXT("Set %d samples on %s\n"), SamplesSet, *WingUtils::FormatName(BS));
}
};

View File

@@ -1,62 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingPackageMaker.h"
#include "Animation/Skeleton.h"
#include "Animation/BlendSpace.h"
#include "BlendSpace_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlendSpace_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new Blend Space (e.g. '/Game/BlendSpaces/BS_Locomotion')"))
FString AssetPath;
UPROPERTY(meta=(Description="Skeleton asset package path"))
FString Skeleton;
virtual FString GetDescription() const override
{
return TEXT("Create a new 2D Blend Space asset with a specified skeleton.");
}
virtual void Handle() override
{
WingPackageMaker Maker(AssetPath);
if (!Maker.Ok()) return;
// Resolve skeleton.
WingFetcher SkeletonFetcher;
USkeleton* SkeletonObj = SkeletonFetcher.Asset(Skeleton).Cast<USkeleton>();
if (!SkeletonObj) return;
// Create the package and Blend Space.
if (!Maker.Make()) return;
UBlendSpace* NewBS = NewObject<UBlendSpace>(Maker.Package(), FName(*Maker.Name()), RF_Public | RF_Standalone);
if (!NewBS)
{
UWingServer::Print(TEXT("ERROR: Failed to create Blend Space object\n"));
return;
}
// Set skeleton.
NewBS->SetSkeleton(SkeletonObj);
NewBS->MarkPackageDirty();
UWingServer::Printf(TEXT("Created %s\n"), *NewBS->GetPathName());
UWingServer::Printf(TEXT("Skeleton: %s\n"), *SkeletonObj->GetPathName());
}
};

View File

@@ -1,242 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphPin.h"
#include "Blueprint_Diff.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Blueprint_Diff : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="First blueprint package path"))
FString BlueprintA;
UPROPERTY(meta=(Description="Second blueprint package path"))
FString BlueprintB;
UPROPERTY(meta=(Optional, Description="Filter to a specific graph name"))
FString Graph;
virtual FString GetDescription() const override
{
return TEXT("Structural diff between two different Blueprints. Compares nodes, "
"connections, and variables across graphs. Use for comparing variants, "
"finding divergence after copy-paste, or auditing consistency.");
}
virtual void Handle() override
{
// Load both blueprints
WingFetcher FA;
UBlueprint* BPA = FA.Asset(BlueprintA).Cast<UBlueprint>();
if (!BPA) return;
WingFetcher FB;
UBlueprint* BPB = FB.Asset(BlueprintB).Cast<UBlueprint>();
if (!BPB) return;
// Gather graphs, optionally filtering by name
auto GatherGraphs = [this](UBlueprint* BP) -> TArray<UEdGraph*>
{
TArray<UEdGraph*> Graphs;
for (UEdGraph* G : BP->UbergraphPages)
{
if (!G) continue;
if (!Graph.IsEmpty() && !WingUtils::Identifies(Graph, G)) continue;
Graphs.Add(G);
}
for (UEdGraph* G : BP->FunctionGraphs)
{
if (!G) continue;
if (!Graph.IsEmpty() && !WingUtils::Identifies(Graph, G)) continue;
Graphs.Add(G);
}
return Graphs;
};
TArray<UEdGraph*> GraphsA = GatherGraphs(BPA);
TArray<UEdGraph*> GraphsB = GatherGraphs(BPB);
// Build graph name maps
TMap<FString, UEdGraph*> GraphMapA, GraphMapB;
for (UEdGraph* G : GraphsA) GraphMapA.Add(WingUtils::FormatName(G), G);
for (UEdGraph* G : GraphsB) GraphMapB.Add(WingUtils::FormatName(G), G);
// Find all unique graph names
TSet<FString> AllGraphNames;
for (auto& Pair : GraphMapA) AllGraphNames.Add(Pair.Key);
for (auto& Pair : GraphMapB) AllGraphNames.Add(Pair.Key);
int32 TotalDiffs = 0;
for (const FString& GraphName : AllGraphNames)
{
UEdGraph** pGA = GraphMapA.Find(GraphName);
UEdGraph** pGB = GraphMapB.Find(GraphName);
if (!pGA)
{
UWingServer::Printf(TEXT("Graph %s: only in B (%d nodes)\n"), *GraphName, (*pGB)->Nodes.Num());
TotalDiffs++;
continue;
}
if (!pGB)
{
UWingServer::Printf(TEXT("Graph %s: only in A (%d nodes)\n"), *GraphName, (*pGA)->Nodes.Num());
TotalDiffs++;
continue;
}
// Both exist -- compare nodes
UEdGraph* GA = *pGA;
UEdGraph* GB = *pGB;
// Build node title maps for matching
TMap<FString, TArray<UEdGraphNode*>> NodesA, NodesB;
for (UEdGraphNode* N : GA->Nodes)
{
if (!N) continue;
NodesA.FindOrAdd(WingUtils::FormatName(N)).Add(N);
}
for (UEdGraphNode* N : GB->Nodes)
{
if (!N) continue;
NodesB.FindOrAdd(WingUtils::FormatName(N)).Add(N);
}
// Nodes only in A
TArray<FString> OnlyInA;
for (auto& Pair : NodesA)
{
int32 CountA = Pair.Value.Num();
int32 CountB = 0;
if (auto* pArr = NodesB.Find(Pair.Key)) CountB = pArr->Num();
if (CountA > CountB)
OnlyInA.Add(FString::Printf(TEXT(" %s (x%d)"), *Pair.Key, CountA - CountB));
}
// Nodes only in B
TArray<FString> OnlyInB;
for (auto& Pair : NodesB)
{
int32 CountB = Pair.Value.Num();
int32 CountA = 0;
if (auto* pArr = NodesA.Find(Pair.Key)) CountA = pArr->Num();
if (CountB > CountA)
OnlyInB.Add(FString::Printf(TEXT(" %s (x%d)"), *Pair.Key, CountB - CountA));
}
// Connection diff
auto MakeConnKey = [](UEdGraphPin* SrcPin, UEdGraphPin* TgtPin) -> FString
{
return FString::Printf(TEXT("%s|%s|%s|%s"),
*WingUtils::FormatName(SrcPin->GetOwningNode()), *WingUtils::FormatName(SrcPin),
*WingUtils::FormatName(TgtPin->GetOwningNode()), *WingUtils::FormatName(TgtPin));
};
auto GatherConnections = [&MakeConnKey](UEdGraph* G) -> TSet<FString>
{
TSet<FString> Conns;
for (UEdGraphNode* N : G->Nodes)
{
if (!N) continue;
for (UEdGraphPin* Pin : N->Pins)
{
if (!Pin || Pin->Direction != EGPD_Output) continue;
for (UEdGraphPin* Linked : Pin->LinkedTo)
{
if (!Linked || !Linked->GetOwningNode()) continue;
Conns.Add(MakeConnKey(Pin, Linked));
}
}
}
return Conns;
};
TSet<FString> ConnectionsA = GatherConnections(GA);
TSet<FString> ConnectionsB = GatherConnections(GB);
TArray<FString> ConnsOnlyInA, ConnsOnlyInB;
for (const FString& Key : ConnectionsA)
if (!ConnectionsB.Contains(Key))
ConnsOnlyInA.Add(FString::Printf(TEXT(" %s"), *Key));
for (const FString& Key : ConnectionsB)
if (!ConnectionsA.Contains(Key))
ConnsOnlyInB.Add(FString::Printf(TEXT(" %s"), *Key));
bool bIdentical = OnlyInA.IsEmpty() && OnlyInB.IsEmpty() && ConnsOnlyInA.IsEmpty() && ConnsOnlyInB.IsEmpty();
if (bIdentical)
{
UWingServer::Printf(TEXT("Graph %s: identical (%d nodes)\n"), *GraphName, GA->Nodes.Num());
continue;
}
TotalDiffs++;
UWingServer::Printf(TEXT("Graph %s: different (A=%d nodes, B=%d nodes)\n"), *GraphName, GA->Nodes.Num(), GB->Nodes.Num());
if (!OnlyInA.IsEmpty())
{
UWingServer::Print(TEXT(" Nodes only in A:\n"));
for (const FString& Line : OnlyInA) UWingServer::Printf(TEXT(" %s\n"), *Line);
}
if (!OnlyInB.IsEmpty())
{
UWingServer::Print(TEXT(" Nodes only in B:\n"));
for (const FString& Line : OnlyInB) UWingServer::Printf(TEXT(" %s\n"), *Line);
}
if (!ConnsOnlyInA.IsEmpty())
{
UWingServer::Print(TEXT(" Connections only in A:\n"));
for (const FString& Line : ConnsOnlyInA) UWingServer::Printf(TEXT(" %s\n"), *Line);
}
if (!ConnsOnlyInB.IsEmpty())
{
UWingServer::Print(TEXT(" Connections only in B:\n"));
for (const FString& Line : ConnsOnlyInB) UWingServer::Printf(TEXT(" %s\n"), *Line);
}
}
// Compare variables
TSet<FString> VarNamesA, VarNamesB;
for (const FBPVariableDescription& V : BPA->NewVariables) VarNamesA.Add(WingUtils::FormatName(V));
for (const FBPVariableDescription& V : BPB->NewVariables) VarNamesB.Add(WingUtils::FormatName(V));
TArray<FString> VarsOnlyInA, VarsOnlyInB;
for (const FString& Name : VarNamesA)
if (!VarNamesB.Contains(Name))
VarsOnlyInA.Add(Name);
for (const FString& Name : VarNamesB)
if (!VarNamesA.Contains(Name))
VarsOnlyInB.Add(Name);
if (!VarsOnlyInA.IsEmpty())
{
UWingServer::Print(TEXT("Variables only in A:\n"));
for (const FString& Name : VarsOnlyInA) UWingServer::Printf(TEXT(" %s\n"), *Name);
TotalDiffs += VarsOnlyInA.Num();
}
if (!VarsOnlyInB.IsEmpty())
{
UWingServer::Print(TEXT("Variables only in B:\n"));
for (const FString& Name : VarsOnlyInB) UWingServer::Printf(TEXT(" %s\n"), *Name);
TotalDiffs += VarsOnlyInB.Num();
}
UWingServer::Printf(TEXT("Total differences: %d\n"), TotalDiffs);
}
};

View File

@@ -1,112 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "UObject/UObjectIterator.h"
#include "Class_Search.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ============================================================
// HandleListClasses — discover available UClasses
// ============================================================
UCLASS()
class UWing_Class_Search : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Optional, Description="Substring filter for class names"))
FString Query;
UPROPERTY(meta=(Optional, Description="Parent class name to restrict results to subclasses"))
FString ParentClass;
UPROPERTY(meta=(Optional, Description="Maximum number of results to return (1-500, default 100)"))
int32 Limit = 100;
virtual FString GetDescription() const override
{
return TEXT("Search for available UClasses by name substring and/or parent class. "
"Returns class names, parent class, package, and flags.");
}
virtual void Handle() override
{
Limit = FMath::Clamp(Limit, 1, 500);
UClass* ParentClassObj = nullptr;
if (!ParentClass.IsEmpty())
{
for (TObjectIterator<UClass> It; It; ++It)
{
if (WingUtils::Identifies(ParentClass, *It))
{
ParentClassObj = *It;
break;
}
}
if (!ParentClassObj)
{
UWingServer::Printf(TEXT("Error: Parent class '%s' not found\n"), *ParentClass);
return;
}
}
TArray<UClass*> Matches;
int32 TotalMatched = 0;
for (TObjectIterator<UClass> It; It; ++It)
{
UClass* Class = *It;
if (!Class) continue;
if (Class->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists)) continue;
if (ParentClassObj && !Class->IsChildOf(ParentClassObj)) continue;
FString ClassName = WingUtils::FormatName(Class);
if (!Query.IsEmpty() && !ClassName.Contains(Query, ESearchCase::IgnoreCase)) continue;
TotalMatched++;
if (Matches.Num() < Limit)
{
Matches.Add(Class);
}
}
UWingServer::Printf(TEXT("Found %d classes"), TotalMatched);
if (TotalMatched > Limit)
{
UWingServer::Printf(TEXT(" (showing %d)"), Limit);
}
UWingServer::Print(TEXT("\n"));
for (UClass* Class : Matches)
{
UWingServer::Printf(TEXT(" %s"), *WingUtils::FormatName(Class));
// Flags
TStringBuilder<64> Flags;
if (Class->HasAnyClassFlags(CLASS_Abstract)) Flags.Append(TEXT(" Abstract"));
if (Class->HasAnyClassFlags(CLASS_Interface)) Flags.Append(TEXT(" Interface"));
if (Class->HasAnyClassFlags(CLASS_MinimalAPI)) Flags.Append(TEXT(" MinimalAPI"));
if (Class->ClassGeneratedBy) Flags.Append(TEXT(" Blueprint"));
if (Flags.Len() > 0)
{
UWingServer::Printf(TEXT(" [%s]"), Flags.ToString() + 1); // skip leading space
}
if (Class->GetSuperClass())
{
UWingServer::Printf(TEXT(" : %s"), *WingUtils::FormatName(Class->GetSuperClass()));
}
UWingServer::Print(TEXT("\n"));
}
}
};

View File

@@ -1,76 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "Class_ShowProperties.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Class_ShowProperties : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Class name to list properties for"))
FString ClassName;
UPROPERTY(meta=(Optional, Description="Substring filter for property names"))
FString Query;
virtual FString GetDescription() const override
{
return TEXT("List properties on a UClass, including type, owning class, and property flags.");
}
virtual void Handle() override
{
UClass* FoundClass = UWingTypes::TextToOneObjectType(ClassName);
if (!FoundClass) return;
UWingServer::Printf(TEXT("Properties of %s:\n"), *WingUtils::FormatName(FoundClass));
int32 Count = 0;
for (TFieldIterator<FProperty> PropIt(FoundClass); PropIt; ++PropIt)
{
FProperty* Prop = *PropIt;
if (!Prop) continue;
FString PropName = Prop->GetName();
if (!Query.IsEmpty() && !PropName.Contains(Query, ESearchCase::IgnoreCase))
continue;
// Build compact flags string
TStringBuilder<256> Flags;
if (Prop->HasAnyPropertyFlags(CPF_BlueprintVisible)) Flags.Append(TEXT(" BlueprintVisible"));
if (Prop->HasAnyPropertyFlags(CPF_BlueprintReadOnly)) Flags.Append(TEXT(" BlueprintReadOnly"));
if (Prop->HasAnyPropertyFlags(CPF_Edit)) Flags.Append(TEXT(" EditAnywhere"));
if (Prop->HasAnyPropertyFlags(CPF_EditConst)) Flags.Append(TEXT(" VisibleOnly"));
if (Prop->HasAnyPropertyFlags(CPF_Config)) Flags.Append(TEXT(" Config"));
if (Prop->HasAnyPropertyFlags(CPF_SaveGame)) Flags.Append(TEXT(" SaveGame"));
if (Prop->HasAnyPropertyFlags(CPF_Transient)) Flags.Append(TEXT(" Transient"));
if (Prop->HasAnyPropertyFlags(CPF_RepNotify)) Flags.Append(TEXT(" RepNotify"));
UClass* OwnerClass = Prop->GetOwnerClass();
UWingServer::Printf(TEXT(" %s %s"), *UWingTypes::TypeToText(Prop), *PropName);
if (OwnerClass && OwnerClass != FoundClass)
UWingServer::Printf(TEXT(" [%s]"), *WingUtils::FormatName(OwnerClass));
if (Flags.Len() > 0)
UWingServer::Printf(TEXT(" (%s)"), Flags.ToString() + 1); // skip leading space
UWingServer::Print(TEXT("\n"));
Count++;
}
if (Count == 0)
{
UWingServer::Print(TEXT("No properties found.\n"));
}
}
};

View File

@@ -1,67 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "Engine/UserDefinedEnum.h"
#include "Kismet2/EnumEditorUtils.h"
#include "Factories/EnumFactory.h"
#include "WingPackageMaker.h"
#include "Enum_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_Enum_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full package path for the new enum (e.g. '/Game/DataTypes/E_MyEnum')"))
FString AssetPath;
UPROPERTY(meta=(Description="Array of enum value names"))
FWingJsonArray Values;
virtual FString GetDescription() const override
{
return TEXT("Create a new UserDefinedEnum asset with the specified values.");
}
virtual void Handle() override
{
WingPackageMaker Maker(AssetPath);
if (!Maker.Ok()) return;
TArray<FString> EnumValues;
for (const TSharedPtr<FJsonValue>& Val : Values.Array)
{
FString Str = Val->AsString();
if (!Str.IsEmpty()) EnumValues.Add(Str);
}
if (EnumValues.Num() == 0)
{
UWingServer::Print(TEXT("ERROR: Values must be a non-empty array of strings\n"));
return;
}
// Create the enum using AssetTools.
UUserDefinedEnum* NewEnum = Maker.CreateAsset<UUserDefinedEnum, UEnumFactory>();
if (!NewEnum) return;
// Add enum values — UUserDefinedEnum starts with a MAX value.
// We need to add entries before MAX.
for (int32 i = 0; i < EnumValues.Num(); ++i)
{
FEnumEditorUtils::AddNewEnumeratorForUserDefinedEnum(NewEnum);
int32 NewIndex = NewEnum->NumEnums() - 2;
FEnumEditorUtils::SetEnumeratorDisplayName(NewEnum, NewIndex, FText::FromString(EnumValues[i]));
}
UWingServer::Printf(TEXT("Created %s with %d values\n"), *NewEnum->GetPathName(), EnumValues.Num());
}
};

View File

@@ -1,49 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingUtils.h"
#include "Materials/MaterialFunction.h"
#include "Factories/MaterialFunctionFactoryNew.h"
#include "WingPackageMaker.h"
#include "MaterialFunction_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_MaterialFunction_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new material function (e.g. '/Game/Materials/MF_MyFunc')"))
FString AssetPath;
UPROPERTY(meta=(Optional, Description="Description for the material function"))
FString Description;
virtual FString GetDescription() const override
{
return TEXT("Create a new UMaterialFunction asset with an optional description.");
}
virtual void Handle() override
{
WingPackageMaker Maker(AssetPath);
if (!Maker.Ok()) return;
// Create via IAssetTools + factory.
UMaterialFunction* MF = Maker.CreateAsset<UMaterialFunction, UMaterialFunctionFactoryNew>();
if (!MF) return;
// Set optional description.
if (!Description.IsEmpty())
MF->Description = Description;
UWingServer::Printf(TEXT("Created %s\n"), *MF->GetPathName());
}
};

View File

@@ -1,116 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "EdGraph/EdGraph.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Animation/AnimBlueprint.h"
#include "Animation/AnimSequence.h"
#include "AnimGraphNode_SequencePlayer.h"
#include "AnimStateNode.h"
#include "AnimationStateMachineGraph.h"
#include "StateMachine_AddState.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_StateMachine_AddState : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Animation Blueprint package path"))
FString Blueprint;
UPROPERTY(meta=(Description="State machine graph name"))
FString Graph;
UPROPERTY(meta=(Description="Name for the new state"))
FString StateName;
UPROPERTY(meta=(Optional, Description="X position of the new state node"))
int32 PosX = 200;
UPROPERTY(meta=(Optional, Description="Y position of the new state node"))
int32 PosY = 0;
UPROPERTY(meta=(Optional, Description="Animation asset package path to assign to the state"))
FString AnimationAsset;
virtual FString GetDescription() const override
{
return TEXT("Add a new state to an animation state machine graph. "
"Optionally assign an animation asset to the state.");
}
virtual void Handle() override
{
// Resolve the anim blueprint
WingFetcher F;
UAnimBlueprint* AnimBP = F.Walk(Blueprint).Cast<UAnimBlueprint>();
if (!AnimBP) return;
// Find the state machine graph
UAnimationStateMachineGraph* SMGraph = WingUtils::FindStateMachineGraph(AnimBP, Graph);
if (!SMGraph)
{
UWingServer::Printf(TEXT("ERROR: State machine graph '%s' not found in %s\n"), *Graph, *WingUtils::FormatName(AnimBP));
return;
}
// Check for duplicate state name
if (WingUtils::FindStateByName(SMGraph, StateName))
{
UWingServer::Printf(TEXT("ERROR: State '%s' already exists in %s\n"), *StateName, *WingUtils::FormatName(SMGraph));
return;
}
// Create the state node
UAnimStateNode* NewState = NewObject<UAnimStateNode>(SMGraph);
NewState->CreateNewGuid();
NewState->NodePosX = PosX;
NewState->NodePosY = PosY;
// Set the state name via the bound graph
NewState->PostPlacedNewNode();
NewState->AllocateDefaultPins();
// Rename the bound graph to set the state name
if (NewState->GetBoundGraph())
{
NewState->GetBoundGraph()->Rename(*StateName, nullptr);
}
SMGraph->AddNode(NewState, false, false);
NewState->SetFlags(RF_Transactional);
// Optionally set animation asset
if (!AnimationAsset.IsEmpty() && NewState->GetBoundGraph())
{
WingFetcher F2;
UAnimSequence* AnimSeq = F2.Asset(AnimationAsset).Cast<UAnimSequence>();
if (!AnimSeq) return;
UAnimGraphNode_SequencePlayer* SeqNode = NewObject<UAnimGraphNode_SequencePlayer>(NewState->GetBoundGraph());
SeqNode->CreateNewGuid();
SeqNode->PostPlacedNewNode();
SeqNode->AllocateDefaultPins();
SeqNode->SetAnimationAsset(AnimSeq);
SeqNode->NodePosX = 0;
SeqNode->NodePosY = 0;
NewState->GetBoundGraph()->AddNode(SeqNode, false, false);
}
// Compile
FKismetEditorUtilities::CompileBlueprint(AnimBP);
UWingServer::Printf(TEXT("Created state '%s' in %s\n"), *StateName, *WingUtils::FormatName(SMGraph));
UWingServer::Printf(TEXT(" node: %s\n"), *WingUtils::FormatName(NewState));
}
};

View File

@@ -1,98 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Animation/AnimBlueprint.h"
#include "AnimStateNode.h"
#include "AnimStateTransitionNode.h"
#include "AnimationStateMachineGraph.h"
#include "StateMachine_AddTransition.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_StateMachine_AddTransition : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Animation Blueprint package path"))
FString Blueprint;
UPROPERTY(meta=(Description="State machine graph name"))
FString Graph;
UPROPERTY(meta=(Description="Name of the source state"))
FString FromState;
UPROPERTY(meta=(Description="Name of the target state"))
FString ToState;
UPROPERTY(meta=(Optional, Description="Crossfade duration in seconds"))
float CrossfadeDuration = 0.0f;
UPROPERTY(meta=(Optional, Description="Transition priority order"))
int32 Priority = 0;
UPROPERTY(meta=(Optional, Description="Whether the transition is bidirectional"))
bool BBidirectional = false;
virtual FString GetDescription() const override
{
return TEXT("Add a transition between two states in an animation state machine graph.");
}
virtual void Handle() override
{
WingFetcher F;
UAnimBlueprint* AnimBP = F.Asset(Blueprint).Cast<UAnimBlueprint>();
if (!AnimBP) return;
UAnimationStateMachineGraph* SMGraph = WingUtils::FindStateMachineGraph(AnimBP, Graph);
if (!SMGraph)
{
UWingServer::Printf(TEXT("ERROR: State machine graph '%s' not found in '%s'\n"), *Graph, *WingUtils::FormatName(AnimBP));
return;
}
UAnimStateNode* FromStateNode = WingUtils::FindStateByName(SMGraph, FromState);
if (!FromStateNode) return;
UAnimStateNode* ToStateNode = WingUtils::FindStateByName(SMGraph, ToState);
if (!ToStateNode) return;
// Create transition node
UAnimStateTransitionNode* TransNode = NewObject<UAnimStateTransitionNode>(SMGraph);
TransNode->CreateNewGuid();
TransNode->PostPlacedNewNode();
TransNode->AllocateDefaultPins();
// Position between the two states
TransNode->NodePosX = (FromStateNode->NodePosX + ToStateNode->NodePosX) / 2;
TransNode->NodePosY = (FromStateNode->NodePosY + ToStateNode->NodePosY) / 2;
SMGraph->AddNode(TransNode, false, false);
TransNode->SetFlags(RF_Transactional);
// Connect: FromState output -> Transition input, Transition output -> ToState input
TransNode->CreateConnections(FromStateNode, ToStateNode);
// Set optional properties
TransNode->CrossfadeDuration = CrossfadeDuration;
TransNode->PriorityOrder = Priority;
TransNode->Bidirectional = BBidirectional;
// Compile
FKismetEditorUtilities::CompileBlueprint(AnimBP);
UWingServer::Printf(TEXT("Created transition %s -> %s: %s\n"),
*FromState, *ToState, *WingUtils::FormatName(TransNode));
}
};

View File

@@ -1,81 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Animation/AnimBlueprint.h"
#include "AnimStateNode.h"
#include "AnimStateTransitionNode.h"
#include "AnimationStateMachineGraph.h"
#include "StateMachine_RemoveState.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_StateMachine_RemoveState : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Path to the state machine graph, e.g. /Game/MyAnimBP,graph:StateMachine"))
FString Graph;
UPROPERTY(meta=(Description="Name of the state to remove"))
FString StateName;
virtual FString GetDescription() const override
{
return TEXT("Remove a state and its connected transitions from an animation state machine graph.");
}
virtual void Handle() override
{
// Fetch the state machine graph via WingFetcher
WingFetcher F;
F.Walk(Graph);
if (!F.Ok()) return;
UAnimationStateMachineGraph* SMGraph = F.Cast<UAnimationStateMachineGraph>();
if (!SMGraph) return;
// Find the owning AnimBlueprint for compile/save
UBlueprint* BP = Cast<UBlueprint>(SMGraph->GetOuter()->GetOuter());
if (!BP)
{
UWingServer::Print(TEXT("ERROR: Could not find owning blueprint.\n"));
return;
}
// Find the state node
UAnimStateNode* StateNode = WingUtils::FindStateByName(SMGraph, StateName);
if (!StateNode) return;
// Collect and remove transitions connected to this state
int32 RemovedTransitions = 0;
for (UEdGraphNode* Node : TArray<UEdGraphNode*>(SMGraph->Nodes))
{
UAnimStateTransitionNode* TransNode = Cast<UAnimStateTransitionNode>(Node);
if (!TransNode) continue;
if (TransNode->GetPreviousState() != StateNode && TransNode->GetNextState() != StateNode) continue;
TransNode->BreakAllNodeLinks();
SMGraph->RemoveNode(TransNode);
RemovedTransitions++;
}
// Remove the state
StateNode->BreakAllNodeLinks();
SMGraph->RemoveNode(StateNode);
// Compile
FKismetEditorUtilities::CompileBlueprint(BP);
UWingServer::Printf(TEXT("Removed state %s and %d transition(s).\n"),
*WingUtils::FormatName(StateNode), RemovedTransitions);
}
};

View File

@@ -1,108 +0,0 @@
#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 "Kismet2/KismetEditorUtilities.h"
#include "Animation/AnimBlueprint.h"
#include "Animation/AnimSequence.h"
#include "AnimGraphNode_SequencePlayer.h"
#include "AnimStateNode.h"
#include "AnimationStateMachineGraph.h"
#include "StateMachine_SetAnimation.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_StateMachine_SetAnimation : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Animation Blueprint package path"))
FString Blueprint;
UPROPERTY(meta=(Description="State machine graph name"))
FString Graph;
UPROPERTY(meta=(Description="Name of the state to modify"))
FString StateName;
UPROPERTY(meta=(Description="Animation asset package path to assign"))
FString AnimationAsset;
virtual FString GetDescription() const override
{
return TEXT("Set or replace the animation sequence played by a state in an animation state machine.");
}
virtual void Handle() override
{
// Resolve the anim blueprint
WingFetcher F;
UAnimBlueprint* AnimBP = F.Walk(Blueprint).Cast<UAnimBlueprint>();
if (!AnimBP) return;
// Find the state machine graph
UAnimationStateMachineGraph* SMGraph = WingUtils::FindStateMachineGraph(AnimBP, Graph);
if (!SMGraph)
{
UWingServer::Printf(TEXT("ERROR: State machine graph '%s' not found in %s\n"), *Graph, *WingUtils::FormatName(AnimBP));
return;
}
// Find the target state
UAnimStateNode* StateNode = WingUtils::FindStateByName(SMGraph, StateName);
if (!StateNode) return;
UEdGraph* InnerGraph = StateNode->GetBoundGraph();
if (!InnerGraph)
{
UWingServer::Printf(TEXT("ERROR: State '%s' has no bound graph\n"), *StateName);
return;
}
// Find the animation asset
WingFetcher F2;
UAnimSequence* AnimSeq = F2.Asset(AnimationAsset).Cast<UAnimSequence>();
if (!AnimSeq) return;
// Find existing SequencePlayer or create one
UAnimGraphNode_SequencePlayer* SeqNode = nullptr;
for (UEdGraphNode* Node : InnerGraph->Nodes)
{
SeqNode = Cast<UAnimGraphNode_SequencePlayer>(Node);
if (SeqNode) break;
}
bool bCreatedNew = false;
if (!SeqNode)
{
SeqNode = NewObject<UAnimGraphNode_SequencePlayer>(InnerGraph);
SeqNode->CreateNewGuid();
SeqNode->PostPlacedNewNode();
SeqNode->AllocateDefaultPins();
SeqNode->NodePosX = 0;
SeqNode->NodePosY = 0;
InnerGraph->AddNode(SeqNode, false, false);
bCreatedNew = true;
}
SeqNode->SetAnimationAsset(AnimSeq);
// Compile
FKismetEditorUtilities::CompileBlueprint(AnimBP);
if (bCreatedNew)
UWingServer::Printf(TEXT("Created sequence player in state '%s', assigned %s\n"), *StateName, *WingUtils::FormatName(AnimSeq));
else
UWingServer::Printf(TEXT("Updated sequence player in state '%s' to %s\n"), *StateName, *WingUtils::FormatName(AnimSeq));
}
};

View File

@@ -1,217 +0,0 @@
#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 "Kismet2/KismetEditorUtilities.h"
#include "Animation/AnimBlueprint.h"
#include "Animation/BlendSpace.h"
#include "AnimGraphNode_BlendSpacePlayer.h"
#include "EdGraphSchema_K2.h"
#include "AnimStateNode.h"
#include "AnimationStateMachineGraph.h"
#include "K2Node_VariableGet.h"
#include "StateMachine_SetBlendSpace.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_StateMachine_SetBlendSpace : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Animation Blueprint package path"))
FString Blueprint;
UPROPERTY(meta=(Description="State machine graph name"))
FString Graph;
UPROPERTY(meta=(Description="Name of the state to modify"))
FString StateName;
UPROPERTY(meta=(Description="Blend Space asset package path"))
FString BlendSpace;
UPROPERTY(meta=(Optional, Description="Blueprint variable name to wire to the X axis input"))
FString XVariable;
UPROPERTY(meta=(Optional, Description="Blueprint variable name to wire to the Y axis input"))
FString YVariable;
virtual FString GetDescription() const override
{
return TEXT("Place a BlendSpacePlayer in a state's inner graph, connect it to the output pose, "
"and optionally wire blueprint variables to the X and Y axis inputs.");
}
virtual void Handle() override
{
// Load the anim blueprint
WingFetcher F;
UAnimBlueprint* AnimBP = F.Asset(Blueprint).Cast<UAnimBlueprint>();
if (!AnimBP) return;
// Find the state machine graph and state
UAnimationStateMachineGraph* SMGraph = WingUtils::FindStateMachineGraph(AnimBP, Graph);
if (!SMGraph) { UWingServer::Printf(TEXT("ERROR: State machine graph '%s' not found\n"), *Graph); return; }
UAnimStateNode* StateNode = WingUtils::FindStateByName(SMGraph, StateName);
if (!StateNode) return;
UEdGraph* InnerGraph = StateNode->GetBoundGraph();
if (!InnerGraph) { UWingServer::Printf(TEXT("ERROR: State '%s' has no bound graph\n"), *StateName); return; }
// Load the blend space asset
WingFetcher F2;
UBlendSpace* BlendSpaceAsset = F2.Asset(BlendSpace).Cast<UBlendSpace>();
if (!BlendSpaceAsset) return;
// Find existing BlendSpacePlayer or create one
UAnimGraphNode_BlendSpacePlayer* BSNode = nullptr;
for (UEdGraphNode* Node : InnerGraph->Nodes)
{
BSNode = Cast<UAnimGraphNode_BlendSpacePlayer>(Node);
if (BSNode) break;
}
if (!BSNode)
{
BSNode = NewObject<UAnimGraphNode_BlendSpacePlayer>(InnerGraph);
BSNode->CreateNewGuid();
BSNode->PostPlacedNewNode();
BSNode->AllocateDefaultPins();
BSNode->NodePosX = 0;
BSNode->NodePosY = 0;
InnerGraph->AddNode(BSNode, false, false);
}
BSNode->SetAnimationAsset(BlendSpaceAsset);
// Connect BlendSpacePlayer output to the Output Animation Pose node
ConnectToOutputPose(BSNode, InnerGraph);
// Wire X and Y variables if provided
WireVariable(AnimBP, InnerGraph, BSNode, XVariable, TEXT("X"));
WireVariable(AnimBP, InnerGraph, BSNode, YVariable, TEXT("Y"));
// Compile
FKismetEditorUtilities::CompileBlueprint(AnimBP);
UWingServer::Printf(TEXT("BlendSpacePlayer %s placed in state %s\n"),
*WingUtils::FormatName(BSNode), *StateName);
}
private:
void ConnectToOutputPose(UAnimGraphNode_BlendSpacePlayer* BSNode, UEdGraph* InnerGraph)
{
// Find the result node (AnimGraphNode_Root or AnimGraphNode_StateResult)
UEdGraphNode* ResultNode = nullptr;
for (UEdGraphNode* Node : InnerGraph->Nodes)
{
if (Node->GetClass()->GetName().Contains(TEXT("AnimGraphNode_Root")) ||
Node->GetClass()->GetName().Contains(TEXT("AnimGraphNode_StateResult")))
{
ResultNode = Node;
break;
}
}
if (!ResultNode) return;
// Find the pose output pin on BlendSpacePlayer and input pin on result node
UEdGraphPin* BSOutputPin = nullptr;
for (UEdGraphPin* Pin : BSNode->Pins)
{
if (Pin && Pin->Direction == EGPD_Output && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct)
{
BSOutputPin = Pin;
break;
}
}
UEdGraphPin* ResultInputPin = nullptr;
for (UEdGraphPin* Pin : ResultNode->Pins)
{
if (Pin && Pin->Direction == EGPD_Input && Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct)
{
ResultInputPin = Pin;
break;
}
}
if (!BSOutputPin || !ResultInputPin) return;
ResultInputPin->BreakAllPinLinks();
const UEdGraphSchema* Schema = InnerGraph->GetSchema();
if (Schema)
Schema->TryCreateConnection(BSOutputPin, ResultInputPin);
}
void WireVariable(UAnimBlueprint* AnimBP, UEdGraph* InnerGraph,
UAnimGraphNode_BlendSpacePlayer* BSNode, const FString& VarName,
const TCHAR* PinName)
{
if (VarName.IsEmpty()) return;
// Verify the variable exists in the blueprint
FName VarFName(*VarName);
bool bVarFound = false;
for (FBPVariableDescription& Var : AnimBP->NewVariables)
{
if (Var.VarName == VarFName)
{
bVarFound = true;
break;
}
}
if (!bVarFound)
{
if (UClass* GenClass = AnimBP->SkeletonGeneratedClass)
{
if (GenClass->FindPropertyByName(VarFName))
bVarFound = true;
}
}
if (!bVarFound)
{
UWingServer::Printf(TEXT("WARNING: Variable '%s' not found, skipping %s wire\n"), *VarName, PinName);
return;
}
// Create a VariableGet node
UK2Node_VariableGet* GetNode = NewObject<UK2Node_VariableGet>(InnerGraph);
GetNode->VariableReference.SetSelfMember(VarFName);
GetNode->NodePosX = BSNode->NodePosX - 250;
GetNode->NodePosY = BSNode->NodePosY;
InnerGraph->AddNode(GetNode, false, false);
GetNode->AllocateDefaultPins();
// Find the variable output pin
UEdGraphPin* VarOutPin = nullptr;
for (UEdGraphPin* Pin : GetNode->Pins)
{
if (Pin && Pin->Direction == EGPD_Output && Pin->PinName == VarFName)
{
VarOutPin = Pin;
break;
}
}
UEdGraphPin* TargetPin = BSNode->FindPin(FName(PinName));
if (VarOutPin && TargetPin)
{
const UEdGraphSchema* Schema = InnerGraph->GetSchema();
if (Schema)
Schema->TryCreateConnection(VarOutPin, TargetPin);
}
}
};

View File

@@ -1,91 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Animation/AnimBlueprint.h"
#include "AnimStateTransitionNode.h"
#include "AnimationStateMachineGraph.h"
#include "StateMachine_SetTransitionRule.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_StateMachine_SetTransitionRule : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Animation Blueprint package path"))
FString Blueprint;
UPROPERTY(meta=(Description="State machine graph name"))
FString Graph;
UPROPERTY(meta=(Description="Name of the source state"))
FString FromState;
UPROPERTY(meta=(Description="Name of the target state"))
FString ToState;
UPROPERTY(meta=(Optional, Description="Crossfade duration in seconds"))
float CrossfadeDuration = 0.0f;
UPROPERTY(meta=(Optional, Description="Blend mode (as integer enum value)"))
int32 BlendMode = 0;
UPROPERTY(meta=(Optional, Description="Transition priority order"))
int32 PriorityOrder = 0;
UPROPERTY(meta=(Optional, Description="Logic type (as integer enum value)"))
int32 LogicType = 0;
UPROPERTY(meta=(Optional, Description="Whether the transition is bidirectional"))
bool BBidirectional = false;
virtual FString GetDescription() const override
{
return TEXT("Update properties on an existing transition between two states in an animation state machine.");
}
virtual void Handle() override
{
WingFetcher F;
UAnimBlueprint* AnimBP = F.Asset(Blueprint).Cast<UAnimBlueprint>();
if (!AnimBP) return;
UAnimationStateMachineGraph* SMGraph = WingUtils::FindStateMachineGraph(AnimBP, Graph);
if (!SMGraph)
{
UWingServer::Printf(TEXT("ERROR: State machine graph '%s' not found in '%s'\n"), *Graph, *WingUtils::FormatName(AnimBP));
return;
}
UAnimStateTransitionNode* TransNode = WingUtils::FindTransition(SMGraph, FromState, ToState);
if (!TransNode)
{
UWingServer::Printf(TEXT("ERROR: Transition from '%s' to '%s' not found in graph '%s'\n"),
*FromState, *ToState, *Graph);
return;
}
// Apply properties
TransNode->CrossfadeDuration = CrossfadeDuration;
TransNode->BlendMode = (EAlphaBlendOption)BlendMode;
TransNode->PriorityOrder = PriorityOrder;
TransNode->LogicType = (ETransitionLogicType::Type)LogicType;
TransNode->Bidirectional = BBidirectional;
// Compile
FKismetEditorUtilities::CompileBlueprint(AnimBP);
UWingServer::Printf(TEXT("Updated transition %s -> %s: %s\n"),
*FromState, *ToState, *WingUtils::FormatName(TransNode));
}
};

View File

@@ -1,95 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingTypes.h"
#include "WingJson.h"
#include "WingUtils.h"
#include "StructUtils/UserDefinedStruct.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "UserDefinedStructure/UserDefinedStructEditorData.h"
#include "Factories/StructureFactory.h"
#include "WingPackageMaker.h"
#include "Struct_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
USTRUCT()
struct FStructPropertyEntry
{
GENERATED_BODY()
UPROPERTY()
FString Name;
UPROPERTY()
FString Type;
};
UCLASS()
class UWing_Struct_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Full asset path for the new struct (e.g. '/Game/DataTypes/S_MyStruct')"))
FString AssetPath;
UPROPERTY(meta=(Optional, Description="Array of initial properties, each with 'name' and 'type' fields"))
FWingJsonArray Properties;
virtual FString GetDescription() const override
{
return TEXT("Create a new UserDefinedStruct asset with optional initial properties.");
}
virtual void Handle() override
{
WingPackageMaker Maker(AssetPath);
if (!Maker.Ok()) return;
// Create the struct using the AssetTools factory.
UUserDefinedStruct* NewStruct = Maker.CreateAsset<UUserDefinedStruct, UStructureFactory>();
if (!NewStruct) return;
// Add properties if specified.
int32 PropsAdded = 0;
for (const TSharedPtr<FJsonValue>& PropVal : Properties.Array)
{
FStructPropertyEntry Entry;
if (!WingJson::PopulateFromJson(FStructPropertyEntry::StaticStruct(), &Entry, PropVal)) return;
if (Entry.Name.IsEmpty() || Entry.Type.IsEmpty()) continue;
FEdGraphPinType PinType;
if (!UWingTypes::TextToType(Entry.Type, PinType))
continue;
// Snapshot existing GUIDs so we can find the newly added one.
TSet<FGuid> ExistingGuids;
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(NewStruct))
ExistingGuids.Add(Var.VarGuid);
if (!FStructureEditorUtils::AddVariable(NewStruct, PinType))
continue;
// Find the new variable by diffing GUID sets.
for (const FStructVariableDescription& Var : FStructureEditorUtils::GetVarDesc(NewStruct))
{
if (!ExistingGuids.Contains(Var.VarGuid))
{
FStructureEditorUtils::RenameVariable(NewStruct, Var.VarGuid, Entry.Name);
break;
}
}
PropsAdded++;
}
UWingServer::Printf(TEXT("Created %s\n"), *NewStruct->GetPathName());
if (PropsAdded > 0)
UWingServer::Printf(TEXT("Properties added: %d\n"), PropsAdded);
}
};

View File

@@ -6,6 +6,7 @@
#include "WingFetcher.h"
#include "WingJson.h"
#include "WingUtils.h"
#include "WingGraphActions.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "EdGraph/EdGraphSchema.h"
@@ -66,26 +67,25 @@ public:
continue;
// Find the action by exact full name
TArray<TSharedPtr<FEdGraphSchemaAction>> Matches = WingUtils::SearchGraphActions(TargetGraph, Entry.ActionName, 2, /*ExactMatch=*/true);
if (Matches.Num() == 0)
FWingGraphActions GA(TargetGraph, Entry.ActionName, 2, /*ExactMatch=*/true);
if (GA.Count() == 0)
{
UWingServer::Printf(TEXT("ERROR: No action found matching '%s'. Use GraphNodeSearchTypes to find available actions.\n"),
*Entry.ActionName);
continue;
}
if (Matches.Num() > 1)
if (GA.Count() > 1)
{
UWingServer::Printf(TEXT("ERROR: Ambiguous: %d actions match '%s'.\n"),
Matches.Num(), *Entry.ActionName);
GA.Count(), *Entry.ActionName);
continue;
}
// Perform the action
FVector2D Location(Entry.PosX, Entry.PosY);
UEdGraphNode* NewNode = Matches[0]->PerformAction(TargetGraph, nullptr, Location, /*bSelectNewNode=*/false);
UEdGraphNode* NewNode = GA.Execute(0, Entry.PosX, Entry.PosY);
if (!NewNode)
{
UWingServer::Printf(TEXT("ERROR: PerformAction returned null for '%s'.\n"), *Entry.ActionName);
UWingServer::Printf(TEXT("ERROR: Execute returned null for '%s'.\n"), *Entry.ActionName);
continue;
}

View File

@@ -5,6 +5,7 @@
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingGraphActions.h"
#include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphSchema.h"
#include "GraphNode_SearchTypes.generated.h"
@@ -44,18 +45,18 @@ public:
UEdGraph* TargetGraph = F.Walk(Graph).Cast<UEdGraph>();
if (!TargetGraph) return;
TArray<TSharedPtr<FEdGraphSchemaAction>> Actions = WingUtils::SearchGraphActions(TargetGraph, Query, ClampedMax, /*ExactMatch=*/false);
FWingGraphActions GA(TargetGraph, Query, ClampedMax, /*ExactMatch=*/false);
for (const TSharedPtr<FEdGraphSchemaAction>& Action : Actions)
for (int32 i = 0; i < GA.Count(); i++)
{
UWingServer::Printf(TEXT("%s\n"), *WingUtils::ActionFullName(Action));
UWingServer::Printf(TEXT("%s\n"), *GA.GetFullName(i));
}
if (Actions.Num() == 0)
if (GA.Count() == 0)
{
UWingServer::Print(TEXT("No matching node types found.\n"));
}
else if (Actions.Num() >= ClampedMax)
else if (GA.Count() >= ClampedMax)
{
UWingServer::Printf(TEXT("WARNING: Reached limit of %d results. Refine your query or increase MaxResults.\n"), ClampedMax);
}

View File

@@ -0,0 +1,138 @@
#include "WingGraphActions.h"
#include "EdGraph/EdGraphSchema.h"
#include "BlueprintActionDatabase.h"
#include "BlueprintNodeSpawner.h"
#include "EdGraphSchema_K2.h"
#include "Kismet2/BlueprintEditorUtils.h"
FString FWingGraphActions::GetFullName(const FEdGraphSchemaAction& Action)
{
FString Category = Action.GetCategory().ToString();
FString MenuName = Action.GetMenuDescription().ToString();
return Category + TEXT("|") + MenuName;
}
FString FWingGraphActions::GetFullName(const UBlueprintNodeSpawner* Spawner)
{
const FBlueprintActionUiSpec& UiSpec = Spawner->PrimeDefaultUiSpec();
FString Category = UiSpec.Category.ToString();
FString MenuName = UiSpec.MenuName.ToString();
return Category + TEXT("|") + MenuName;
}
bool FWingGraphActions::IsMatch(const FEdGraphSchemaAction& Action, const FString &QueryLower, bool Exact)
{
FString FullName = GetFullName(Action).ToLower();
if (FullName.Len() < 3) return false;
if (FullName == QueryLower) return true;
if (Exact) return false;
if (FullName.Contains(QueryLower)) return true;
FString Keywords = Action.GetKeywords().ToString();
if (Keywords.ToLower().Contains(QueryLower)) return true;
return false;
}
bool FWingGraphActions::IsMatch(const UBlueprintNodeSpawner* Spawner, const FString &QueryLower, bool Exact)
{
FString FullName = GetFullName(Spawner).ToLower();
if (FullName.Len() < 3) return false;
if (FullName == QueryLower) return true;
if (Exact) return false;
if (FullName.Contains(QueryLower)) return true;
FString Keywords = Spawner->PrimeDefaultUiSpec().Keywords.ToString();
if (Keywords.ToLower().Contains(QueryLower)) return true;
return false;
}
UEdGraphNode* FWingGraphActions::Execute(FEdGraphSchemaAction& Action, int32 X, int32 Y)
{
FVector2D Location(X, Y);
UEdGraphNode* NewNode = Action.PerformAction(Graph, nullptr, Location, /*bSelectNewNode=*/false);
return NewNode;
}
UEdGraphNode* FWingGraphActions::Execute(UBlueprintNodeSpawner* Spawner, int32 X, int32 Y)
{
FVector2D Location(X, Y);
IBlueprintNodeBinder::FBindingSet Bindings;
return Spawner->Invoke(Graph, Bindings, Location);
}
void FWingGraphActions::CollectActions(UEdGraph* GraphP, const FString& Query, int32 MaxResults, bool ExactMatch)
{
Graph = GraphP;
FString QueryLower = Query.ToLower();
FGraphContextMenuBuilder ContextMenuBuilder(Graph);
Graph->GetSchema()->GetGraphContextActions(ContextMenuBuilder);
for (int32 i = 0; i < ContextMenuBuilder.GetNumActions(); i++)
{
if ((MaxResults > 0) && (Actions.Num() >= MaxResults)) break;
TSharedPtr<FEdGraphSchemaAction> Action = ContextMenuBuilder.GetSchemaAction(i);
if (IsMatch(*Action, QueryLower, ExactMatch)) Actions.Add(Action);
}
}
void FWingGraphActions::CollectSpawners(UEdGraph* GraphP, const FString& Query, int32 MaxResults, bool ExactMatch)
{
Graph = GraphP;
FString QueryLower = Query.ToLower();
for (const auto& Pair : FBlueprintActionDatabase::Get().GetAllActions())
{
for (UBlueprintNodeSpawner* Spawner : Pair.Value)
{
if ((MaxResults > 0) && (Spawners.Num() >= MaxResults)) break;
if (!Spawner) continue;
// Filter by graph compatibility if a graph was provided
if (Spawner->NodeClass)
{
UEdGraphNode* NodeCDO = CastChecked<UEdGraphNode>(Spawner->NodeClass->ClassDefaultObject);
if (!NodeCDO->IsCompatibleWithGraph(Graph))
{
continue;
}
}
if (IsMatch(Spawner, QueryLower, ExactMatch)) Spawners.Add(Spawner);
}
}
}
FString FWingGraphActions::GetFullName(int N)
{
if (N < Actions.Num())
{
return GetFullName(*Actions[N]);
}
else
{
return GetFullName(Spawners[N - Actions.Num()]);
}
}
UEdGraphNode* FWingGraphActions::Execute(int32 N, int32 PosX, int32 PosY)
{
if (N < Actions.Num())
{
return Execute(*Actions[N], PosX, PosY);
}
else
{
return Execute(Spawners[N - Actions.Num()], PosX, PosY);
}
}
FWingGraphActions::FWingGraphActions(UEdGraph *Graph, const FString& Query, int32 MaxResults, bool ExactMatch)
{
if (Cast<UEdGraphSchema_K2>(Graph->GetSchema()))
{
CollectSpawners(Graph, Query, MaxResults, ExactMatch);
}
else
{
CollectActions(Graph, Query, MaxResults, ExactMatch);
}
}

View File

@@ -471,55 +471,6 @@ UAnimStateTransitionNode* WingUtils::FindTransition(UAnimationStateMachineGraph*
return nullptr;
}
// ============================================================
// Graph actions (node spawning)
// ============================================================
FString WingUtils::ActionFullName(const TSharedPtr<FEdGraphSchemaAction>& Action)
{
FString Category = Action->GetCategory().ToString();
FString MenuName = Action->GetMenuDescription().ToString();
if (Category.IsEmpty())
return MenuName;
return Category + TEXT("|") + MenuName;
}
TArray<TSharedPtr<FEdGraphSchemaAction>> WingUtils::SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults, bool ExactMatch)
{
FString QueryLower = Query.ToLower();
TArray<TSharedPtr<FEdGraphSchemaAction>> Result;
FGraphContextMenuBuilder ContextMenuBuilder(Graph);
Graph->GetSchema()->GetGraphContextActions(ContextMenuBuilder);
for (int32 i = 0; i < ContextMenuBuilder.GetNumActions(); i++)
{
TSharedPtr<FEdGraphSchemaAction> Action = ContextMenuBuilder.GetSchemaAction(i);
if (!Action.IsValid()) continue;
FString FullName = ActionFullName(Action);
if (FullName.IsEmpty()) continue;
if (ExactMatch)
{
if (FullName.ToLower() != QueryLower)
continue;
}
else
{
FString Keywords = Action->GetKeywords().ToString();
if (!FullName.ToLower().Contains(QueryLower) && !Keywords.ToLower().Contains(QueryLower))
continue;
}
Result.Add(Action);
if ((MaxResults > 0) && (Result.Num() >= MaxResults))
break;
}
return Result;
}
// ============================================================
// Support for locating UE Wingman Handlers
// ============================================================

View File

@@ -0,0 +1,51 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraph.h"
class UBlueprintNodeSpawner;
struct FEdGraphSchemaAction;
// Holds a collection of graph actions, populated from either the
// BlueprintActionDatabase (for K2 graphs) or GetGraphContextActions
// (for everything else).
struct FWingGraphActions
{
public:
// Constructor populates the list of actions.
FWingGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults = 0, bool ExactMatch=false);
// Get the number of results.
int Count() const { return Spawners.Num() + Actions.Num(); }
// Get the name of the nth result.
FString GetFullName(int32 N);
// Execute the nth result, which should create a graph node.
// If it can't, it will print an error and return nullptr.
UEdGraphNode *Execute(int32 N, int32 PosX, int32 PosY);
private:
// The Graph that we're generating Actions for.
UEdGraph *Graph;
// One of these two will be populated, depending on graph type.
TArray<TSharedPtr<FEdGraphSchemaAction>> Actions;
TArray<UBlueprintNodeSpawner*> Spawners;
// Get the full name of an action.
FString GetFullName(const FEdGraphSchemaAction& Action);
FString GetFullName(const UBlueprintNodeSpawner* Spawner);
// Compare an action against a query.
bool IsMatch(const FEdGraphSchemaAction& Action, const FString &QueryLower, bool Exact);
bool IsMatch(const UBlueprintNodeSpawner* Spawner, const FString &QueryLower, bool Exact);
// Execute an action
UEdGraphNode* Execute(FEdGraphSchemaAction& Action, int32 X, int32 Y);
UEdGraphNode* Execute(UBlueprintNodeSpawner* Spawner, int32 X, int32 Y);
// Routines that collect actions and spawners.
void CollectActions(UEdGraph* GraphP, const FString& Query, int32 MaxResults, bool ExactMatch);
void CollectSpawners(UEdGraph* GraphP, const FString& Query, int32 MaxResults, bool ExactMatch);
};

View File

@@ -230,10 +230,6 @@ public:
static UAnimStateNode* FindStateByName(UAnimationStateMachineGraph* SMGraph, const FString& StateName);
static UAnimStateTransitionNode* FindTransition(UAnimationStateMachineGraph* SMGraph, const FString& FromStateName, const FString& ToStateName);
// ----- Graph actions (node spawning) -----
static FString ActionFullName(const TSharedPtr<FEdGraphSchemaAction>& Action);
static TArray<TSharedPtr<FEdGraphSchemaAction>> SearchGraphActions(UEdGraph* Graph, const FString& Query, int32 MaxResults = 0, bool ExactMatch = false);
// ----- Text formatting -----
static FString WrapText(const FString& Text, int32 ColLimit, const FString& Prefix);