WingVariables for the win

This commit is contained in:
2026-03-31 16:43:28 -04:00
parent 43f2439f29
commit d590d71421
23 changed files with 269 additions and 964 deletions

View File

@@ -5,12 +5,9 @@
#include "WingHandler.h" #include "WingHandler.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "WingFunctionArgs.h" #include "WingVariables.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "EdGraph/EdGraph.h" #include "EdGraph/EdGraph.h"
#include "EdGraph/EdGraphNode.h"
#include "K2Node_EditablePinBase.h"
#include "K2Node_FunctionResult.h"
#include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "BlueprintGraph_Create.generated.h" #include "BlueprintGraph_Create.generated.h"
@@ -35,11 +32,11 @@ public:
UPROPERTY(meta=(Description="Type of graph: function or macro")) UPROPERTY(meta=(Description="Type of graph: function or macro"))
FString GraphType; FString GraphType;
UPROPERTY(meta=(Description="Arguments expressed as: int x,float y")) UPROPERTY(meta=(Optional, Description="Input variables, one per line"))
FString Arguments; FString InputVariables;
UPROPERTY(meta=(Description="Return values expressed as: int x,float y")) UPROPERTY(meta=(Optional, Description="Output variables, one per line"))
FString ReturnValues; FString OutputVariables;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
@@ -81,9 +78,10 @@ public:
if (!WingUtils::FindNoneWithInternalID(InternalID, WingUtils::AllGraphs(BP), TEXT("Graph"))) if (!WingUtils::FindNoneWithInternalID(InternalID, WingUtils::AllGraphs(BP), TEXT("Graph")))
return; return;
// Validate argument and return value types before making changes // Parse and validate variables before making changes
if (!Arguments.IsEmpty() && !WingFunctionArgs::CheckArgs(Arguments)) return; WingVariables Vars;
if (!ReturnValues.IsEmpty() && !WingFunctionArgs::CheckArgs(ReturnValues)) return; if (!Vars.InputVariables.ParseString(InputVariables)) return;
if (!Vars.OutputVariables.ParseString(OutputVariables)) return;
// Create the Graph // Create the Graph
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, InternalID, UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(BP, InternalID,
@@ -104,17 +102,10 @@ public:
FBlueprintEditorUtils::AddMacroGraph(BP, NewGraph, /*bIsUserCreated=*/true, /*SignatureFromClass=*/nullptr); FBlueprintEditorUtils::AddMacroGraph(BP, NewGraph, /*bIsUserCreated=*/true, /*SignatureFromClass=*/nullptr);
} }
// Try GetEntryAndResultNodes first (works for both functions and macros) // Create the variables on the new graph
TWeakObjectPtr<UK2Node_EditablePinBase> Entry; if (!Vars.SetBackingStore(NewGraph)) return;
TWeakObjectPtr<UK2Node_EditablePinBase> Exit; if (!Vars.Check()) return;
FBlueprintEditorUtils::GetEntryAndResultNodes(NewGraph, Entry, Exit); if (!Vars.Create()) return;
if ((Entry == nullptr) || (Exit == nullptr))
{
UWingServer::Printf(TEXT("Could not get graph entry and exit nodes.\n"));
return;
}
if (!WingFunctionArgs::SetArgs(Entry.Get(), Arguments)) return;
if (!WingFunctionArgs::SetArgs(Exit.Get(), ReturnValues)) return;
UWingServer::Printf(TEXT("Created %s graph: %s\n"), *GraphType, *WingUtils::FormatName(NewGraph)); UWingServer::Printf(TEXT("Created %s graph: %s\n"), *GraphType, *WingUtils::FormatName(NewGraph));
} }

View File

@@ -1,50 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingTypes.h"
#include "WingVariables.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "BlueprintVariable_Create.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintVariable_Create : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint path"))
FString Blueprint;
UPROPERTY(meta=(Description="Variable declarations"))
FString Variables;
virtual FString GetDescription() const override
{
return TEXT("Add new member variables to a Blueprint. "
"Format: 'type name (flags) = default', one per line.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
// Parse the variable declarations.
WingVariables Vars(BP);
if (!Vars.BlueprintVariables.ParseString(Variables)) return;
if (!Vars.Check()) return;
if (!Vars.Create()) return;
UWingServer::Printf(TEXT("Success.\n"));
}
};

View File

@@ -1,49 +0,0 @@
#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;
FWingBlueprintVar 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

@@ -1,39 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingVariables.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;
virtual FString GetDescription() const override
{
return TEXT("List all member variables of a Blueprint.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
WingVariables Vars(BP);
Vars.Load();
Vars.Print(UWingServer::GetPrintBuffer());
}
};

View File

@@ -1,48 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingVariables.h"
#include "Engine/Blueprint.h"
#include "Kismet2/KismetEditorUtilities.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="Variable declarations, one per line. Format: type name (flags) = default"))
FString Variables;
virtual FString GetDescription() const override
{
return TEXT("Modify existing Blueprint variables. Format: 'type name (flags) = default', one per line.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
// Parse the variable declarations.
WingVariables Vars(BP);
if (!Vars.BlueprintVariables.ParseString(Variables)) return;
if (!Vars.Check()) return;
if (!Vars.Modify()) return;
UWingServer::Printf(TEXT("Success.\n"));
}
};

View File

@@ -1,43 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingVariables.h"
#include "BlueprintVariable_Remove.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_BlueprintVariable_Remove : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint path"))
FString Blueprint;
UPROPERTY(meta=(Description="Variable names to remove, comma-separated"))
FString Variables;
virtual FString GetDescription() const override
{
return TEXT("Remove member variables from a Blueprint.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
WingVariables Vars(BP);
if (!Vars.BlueprintVariables.ParseNamesString(Variables)) return;
if (!Vars.Remove()) return;
UWingServer::Printf(TEXT("Success.\n"));
}
};

View File

@@ -10,7 +10,6 @@
#include "Animation/AnimBlueprint.h" #include "Animation/AnimBlueprint.h"
#include "Animation/Skeleton.h" #include "Animation/Skeleton.h"
#include "WingActorComponent.h" #include "WingActorComponent.h"
#include "WingFunctionArgs.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
#include "AnimationGraph.h" #include "AnimationGraph.h"
#include "AnimationGraphSchema.h" #include "AnimationGraphSchema.h"
@@ -66,25 +65,8 @@ public:
} }
// Variables // Variables
if (!BP->NewVariables.IsEmpty()) WingVariables BlueprintVars;
{ BlueprintVars.SetBackingStore(BP);
UWingServer::Print(TEXT("\nVariables:\n"));
for (const FBPVariableDescription& V : BP->NewVariables)
{
if (V.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate) continue;
UWingServer::Printf(TEXT(" %s %s"),
*UWingTypes::TypeToText(V.VarType),
*WingUtils::FormatName(V));
if (!V.DefaultValue.IsEmpty())
UWingServer::Printf(TEXT(" = %s"), *V.DefaultValue);
if (!V.Category.IsEmpty() && V.Category.ToString() != TEXT("Default"))
UWingServer::Printf(TEXT(" [%s]"), *V.Category.ToString());
UWingServer::Print(TEXT("\n"));
}
}
// Variables (new format)
WingVariables BlueprintVars(BP);
BlueprintVars.Load(); BlueprintVars.Load();
BlueprintVars.Print(UWingServer::GetPrintBuffer()); BlueprintVars.Print(UWingServer::GetPrintBuffer());
@@ -192,38 +174,32 @@ public:
private: private:
void PrintEventDispatcher(UEdGraph* Graph) void PrintEventDispatcher(UEdGraph* Graph)
{ {
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode; WingVariables Vars;
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode; Vars.SetBackingStore(Graph);
FBlueprintEditorUtils::GetEntryAndResultNodes(Graph, EntryNode, ResultNode); Vars.Load();
FString Args; FStringBuilderBase &Out = UWingServer::GetPrintBuffer();
if (EntryNode.IsValid() && WingFunctionArgs::HasArgs(EntryNode.Get())) Out.Appendf(TEXT(" %s("), *WingUtils::FormatName(Graph));
Args = WingFunctionArgs::GetArgs(EntryNode.Get()); Vars.InputVariables.PrintCompact(Out);
Out.Append(TEXT(")\n"));
UWingServer::Printf(TEXT(" %s(%s)\n"), *WingUtils::FormatName(Graph), *Args);
} }
void PrintGraph(UEdGraph* Graph, const TCHAR* Type, UClass* Interface = nullptr) void PrintGraph(UEdGraph* Graph, const TCHAR* Type, UClass* Interface = nullptr)
{ {
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode; WingVariables Vars;
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode; Vars.SetBackingStore(Graph);
FBlueprintEditorUtils::GetEntryAndResultNodes(Graph, EntryNode, ResultNode); Vars.Load();
FString InputArgs; FStringBuilderBase &Out = UWingServer::GetPrintBuffer();
FString OutputArgs; Out.Appendf(TEXT(" %s %s"), Type, *WingUtils::FormatName(Graph));
if (EntryNode.IsValid() && WingFunctionArgs::HasArgs(EntryNode.Get())) Out.AppendChar('(');
InputArgs = WingFunctionArgs::GetArgs(EntryNode.Get()); Vars.InputVariables.PrintCompact(Out);
if (ResultNode.IsValid() && WingFunctionArgs::HasArgs(ResultNode.Get())) Out.Append(TEXT(") -> ("));
OutputArgs = WingFunctionArgs::GetArgs(ResultNode.Get()); Vars.OutputVariables.PrintCompact(Out);
Out.AppendChar(')');
UWingServer::Printf(TEXT(" %s %s"), Type, *WingUtils::FormatName(Graph));
if (!InputArgs.IsEmpty())
UWingServer::Printf(TEXT("(%s)"), *InputArgs);
if (!OutputArgs.IsEmpty())
UWingServer::Printf(TEXT(" -> (%s)"), *OutputArgs);
if (Interface) if (Interface)
UWingServer::Printf(TEXT(" [%s]"), *WingUtils::FormatName(Interface)); Out.Appendf(TEXT(" [%s]"), *WingUtils::FormatName(Interface));
UWingServer::Print(TEXT("\n")); Out.AppendChar('\n');
} }
}; };

View File

@@ -4,7 +4,6 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingHandler.h" #include "WingHandler.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingFunctionArgs.h"
#include "WingUtils.h" #include "WingUtils.h"
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
#include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2.h"
@@ -28,8 +27,8 @@ public:
UPROPERTY(meta=(Description="Name of the new event dispatcher")) UPROPERTY(meta=(Description="Name of the new event dispatcher"))
FString Dispatcher; FString Dispatcher;
UPROPERTY(meta=(Description="Arguments expressed as: int x,float y")) UPROPERTY(meta=(Description="Input Variables, one per line, expressed as: type var = value"))
FString Arguments; FString InputVariables;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
@@ -45,11 +44,13 @@ public:
// Check for valid proposed name // Check for valid proposed name
FName InternalID = WingUtils::CheckProposedName(Dispatcher); FName InternalID = WingUtils::CheckProposedName(Dispatcher);
if (InternalID.IsNone()) return; if (InternalID.IsNone()) return;
if (!WingUtils::FindNoneWithInternalID(InternalID, BP->NewVariables, TEXT("Variable"))) return; TSet<FName> Names;
if (!WingUtils::FindNoneWithInternalID(InternalID, WingUtils::AllGraphs(BP), TEXT("Graph"))) return; FBlueprintEditorUtils::GetClassVariableList(BP, Names);
if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("variable or component"))) return;
// Make sure the argument types are valid. // Parse the arguments.
if (!WingFunctionArgs::CheckArgs(Arguments)) return; WingVariables Vars;
if (!Vars.InputVariables.ParseString(InputVariables)) return;
// Add the delegate variable // Add the delegate variable
FEdGraphPinType DelegateType; FEdGraphPinType DelegateType;
@@ -78,16 +79,9 @@ public:
BP->DelegateSignatureGraphs.Add(SigGraph); BP->DelegateSignatureGraphs.Add(SigGraph);
// Store the function arguments // Store the function arguments
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode; if (!Vars.SetBackingStore(SigGraph)) return;
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode; if (!Vars.Check()) return;
FBlueprintEditorUtils::GetEntryAndResultNodes(SigGraph, EntryNode, ResultNode); if (!Vars.Create()) return;
if (!EntryNode.IsValid())
{
UWingServer::Print(TEXT("ERROR: Entry node not found in delegate signature graph\n"));
return;
}
if (!WingFunctionArgs::SetArgs(EntryNode.Get(), Arguments)) return;
UWingServer::Printf(TEXT("Created event dispatcher %s in %s\n"), *Dispatcher, *WingUtils::FormatName(BP)); UWingServer::Printf(TEXT("Created event dispatcher %s in %s\n"), *Dispatcher, *WingUtils::FormatName(BP));
} }
}; };

View File

@@ -1,60 +0,0 @@
#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 "WingFunctionArgs.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "EventDispatcher_Dump.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_EventDispatcher_Dump : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Name of the event dispatcher to inspect"))
FString Dispatcher;
virtual FString GetDescription() const override
{
return TEXT("Show all editable properties of a Blueprint event dispatcher.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
FBPVariableDescription* Var = WingUtils::FindOneWithExternalID(Dispatcher, BP->NewVariables, TEXT("Dispatcher"));
if (!Var) return;
TObjectPtr<UEdGraph>* SigGraph = WingUtils::FindOneWithExternalID(Dispatcher, BP->DelegateSignatureGraphs, TEXT("Dispatcher Signature Graph"));
if (!SigGraph) return;
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode;
FBlueprintEditorUtils::GetEntryAndResultNodes(*SigGraph, EntryNode, ResultNode);
if (!EntryNode.IsValid())
{
UWingServer::Print(TEXT("ERROR: Entry node not found in delegate signature graph\n"));
return;
}
FString Args = WingFunctionArgs::GetArgs(EntryNode.Get());
UWingServer::Printf(TEXT("Event dispatcher %s in %s:\n"), *Dispatcher, *WingUtils::FormatName(BP));
UWingServer::Printf(TEXT(" Arguments: %s\n"), *Args);
}
};

View File

@@ -1,64 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingServer.h"
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingFunctionArgs.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "EventDispatcher_Modify.generated.h"
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
UCLASS()
class UWing_EventDispatcher_Modify : public UObject, public IWingHandler
{
GENERATED_BODY()
public:
UPROPERTY(meta=(Description="Blueprint name or package path"))
FString Blueprint;
UPROPERTY(meta=(Description="Name of the event dispatcher to modify"))
FString Dispatcher;
UPROPERTY(meta=(Description="New arguments expressed as: int x,float y"))
FString Arguments;
virtual FString GetDescription() const override
{
return TEXT("Modify the arguments of an existing Blueprint event dispatcher.");
}
virtual void Handle() override
{
WingFetcher F;
UBlueprint* BP = F.Walk(Blueprint).Cast<UBlueprint>();
if (!BP) return;
FBPVariableDescription* Var = WingUtils::FindOneWithExternalID(Dispatcher, BP->NewVariables, TEXT("Dispatcher"));
if (!Var) return;
TObjectPtr<UEdGraph>* SigGraph = WingUtils::FindOneWithExternalID(Dispatcher, BP->DelegateSignatureGraphs, TEXT("Dispatcher Signature Graph"));
if (!SigGraph) return;
// Make sure the argument types are valid.
if (!WingFunctionArgs::CheckArgs(Arguments)) return;
TWeakObjectPtr<UK2Node_EditablePinBase> EntryNode;
TWeakObjectPtr<UK2Node_EditablePinBase> ResultNode;
FBlueprintEditorUtils::GetEntryAndResultNodes(*SigGraph, EntryNode, ResultNode);
if (!EntryNode.IsValid())
{
UWingServer::Print(TEXT("ERROR: Entry node not found in delegate signature graph\n"));
return;
}
if (!WingFunctionArgs::SetArgs(EntryNode.Get(), Arguments)) return;
UWingServer::Printf(TEXT("Modified event dispatcher %s in %s\n"), *Dispatcher, *WingUtils::FormatName(BP));
}
};

View File

@@ -1,54 +0,0 @@
#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 (FunctionEntry, FunctionResult, CustomEvent, or Tunnel)"))
FString Node;
UPROPERTY(meta=(Description="Parameter list, such as 'int x,float y'"))
FString Args;
UPROPERTY(meta=(Optional, Description="Also rename the node (which renames a Function or Custom Event)"))
FString Rename;
virtual FString GetDescription() const override
{
return TEXT("Set the parameter list of a FunctionEntry, FunctionResult, CustomEvent, 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"));
UWingServer::SuggestManual(WingManual::Section::HandlerHelp);
return;
}
if (!Rename.IsEmpty())
{
if (!WingUtils::CheckCanRename(NodeObj, Rename)) return;
NodeObj->OnRenameNode(Rename);
}
if (!WingFunctionArgs::SetArgs(NodeObj, Args)) return;
UWingServer::Printf(TEXT("Args set to: %s\n"), *WingFunctionArgs::GetArgs(NodeObj));
}
};

View File

@@ -5,11 +5,7 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingVariables.h" #include "WingVariables.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Variables_Create.generated.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "K2Node_EditablePinBase.h"
#include "K2Node_FunctionEntry.h"
#include "GraphVariables_Create.generated.h"
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -17,36 +13,40 @@
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
UCLASS() UCLASS()
class UWing_GraphVariables_Create : public UObject, public IWingHandler class UWing_Variables_Create : public UObject, public IWingHandler
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Description="Path to a function or macro graph (e.g. '/Game/MyBP,graph:MyFunction')")) UPROPERTY(meta=(Description="Path to a blueprint, graph, or custom event node"))
FString Graph; FString Object;
UPROPERTY(meta=(Optional, Description="Inputs to the graph")) UPROPERTY(meta=(Optional, Description="Blueprint variables, one per line"))
FString BlueprintVariables;
UPROPERTY(meta=(Optional, Description="Input variables, one per line"))
FString InputVariables; FString InputVariables;
UPROPERTY(meta=(Optional, Description="Outputs to the graph")) UPROPERTY(meta=(Optional, Description="Output variables, one per line"))
FString OutputVariables; FString OutputVariables;
UPROPERTY(meta=(Optional, Description="Locals to the graph")) UPROPERTY(meta=(Optional, Description="Local variables, one per line"))
FString LocalVariables; FString LocalVariables;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("Add new inputs, outputs, and local variables to a graph. " return TEXT("Add new variables. Format: 'type name (flags) = default', one per line.");
"Format: 'type name (flags) = default', one per line");
} }
virtual void Handle() override virtual void Handle() override
{ {
WingFetcher F; WingFetcher F;
UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>(); UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!G) return; if (!Obj) return;
WingVariables Vars(G); WingVariables Vars;
if (!Vars.SetBackingStore(Obj)) return;
if (!Vars.BlueprintVariables.ParseString(BlueprintVariables)) return;
if (!Vars.InputVariables.ParseString(InputVariables)) return; if (!Vars.InputVariables.ParseString(InputVariables)) return;
if (!Vars.OutputVariables.ParseString(OutputVariables)) return; if (!Vars.OutputVariables.ParseString(OutputVariables)) return;
if (!Vars.LocalVariables.ParseString(LocalVariables)) return; if (!Vars.LocalVariables.ParseString(LocalVariables)) return;

View File

@@ -5,8 +5,7 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingVariables.h" #include "WingVariables.h"
#include "Kismet2/KismetEditorUtilities.h" #include "Variables_Dump.generated.h"
#include "GraphVariables_Dump.generated.h"
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -14,26 +13,28 @@
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
UCLASS() UCLASS()
class UWing_GraphVariables_Dump : public UObject, public IWingHandler class UWing_Variables_Dump : public UObject, public IWingHandler
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Description="Path to a function graph (e.g. '/Game/MyBP,graph:MyFunction')")) UPROPERTY(meta=(Description="Path to a blueprint, graph, or custom event node"))
FString Graph; FString Object;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("List all arguments, return values, and local variables of a function graph."); return TEXT("List all variables of a blueprint, function graph,"
"macro graph, event dispatcher graph, or custom event node");
} }
virtual void Handle() override virtual void Handle() override
{ {
WingFetcher F; WingFetcher F;
UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>(); UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!G) return; if (!Obj) return;
WingVariables Vars(G); WingVariables Vars;
if (!Vars.SetBackingStore(Obj)) return;
Vars.Load(); Vars.Load();
Vars.Print(UWingServer::GetPrintBuffer()); Vars.Print(UWingServer::GetPrintBuffer());
} }

View File

@@ -5,7 +5,7 @@
#include "WingServer.h" #include "WingServer.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingVariables.h" #include "WingVariables.h"
#include "GraphVariables_Modify.generated.h" #include "Variables_Modify.generated.h"
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -13,35 +13,41 @@
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
UCLASS() UCLASS()
class UWing_GraphVariables_Modify : public UObject, public IWingHandler class UWing_Variables_Modify : public UObject, public IWingHandler
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Description="Path to a graph")) UPROPERTY(meta=(Description="Path to a blueprint, graph, or custom event node"))
FString Graph; FString Object;
UPROPERTY(meta=(Optional, Description="Inputs to the graph")) UPROPERTY(meta=(Optional, Description="Blueprint variables, one per line"))
FString BlueprintVariables;
UPROPERTY(meta=(Optional, Description="Input variables, one per line"))
FString InputVariables; FString InputVariables;
UPROPERTY(meta=(Optional, Description="Outputs to the graph")) UPROPERTY(meta=(Optional, Description="Output variables, one per line"))
FString OutputVariables; FString OutputVariables;
UPROPERTY(meta=(Optional, Description="Locals to the graph")) UPROPERTY(meta=(Optional, Description="Local variables, one per line"))
FString LocalVariables; FString LocalVariables;
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("Modify existing arguments, return values, and local variables of a graph."); return TEXT("Modify variables of a blueprint, function graph, "
"macro graph, event dispatcher graph, or custom event node. ");
} }
virtual void Handle() override virtual void Handle() override
{ {
WingFetcher F; WingFetcher F;
UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>(); UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!G) return; if (!Obj) return;
WingVariables Vars(G); WingVariables Vars;
if (!Vars.SetBackingStore(Obj)) return;
if (!Vars.BlueprintVariables.ParseString(BlueprintVariables)) return;
if (!Vars.InputVariables.ParseString(InputVariables)) return; if (!Vars.InputVariables.ParseString(InputVariables)) return;
if (!Vars.OutputVariables.ParseString(OutputVariables)) return; if (!Vars.OutputVariables.ParseString(OutputVariables)) return;
if (!Vars.LocalVariables.ParseString(LocalVariables)) return; if (!Vars.LocalVariables.ParseString(LocalVariables)) return;

View File

@@ -5,7 +5,7 @@
#include "WingHandler.h" #include "WingHandler.h"
#include "WingFetcher.h" #include "WingFetcher.h"
#include "WingVariables.h" #include "WingVariables.h"
#include "GraphVariables_Remove.generated.h" #include "Variables_Remove.generated.h"
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
@@ -13,13 +13,16 @@
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
UCLASS() UCLASS()
class UWing_GraphVariables_Remove : public UObject, public IWingHandler class UWing_Variables_Remove : public UObject, public IWingHandler
{ {
GENERATED_BODY() GENERATED_BODY()
public: public:
UPROPERTY(meta=(Description="Path to a function or macro graph")) UPROPERTY(meta=(Description="Path to a blueprint, graph, or custom event node"))
FString Graph; FString Object;
UPROPERTY(meta=(Optional, Description="Blueprint variable names to remove, comma-separated"))
FString BlueprintVariables;
UPROPERTY(meta=(Optional, Description="Input variable names to remove, comma-separated")) UPROPERTY(meta=(Optional, Description="Input variable names to remove, comma-separated"))
FString InputVariables; FString InputVariables;
@@ -32,16 +35,18 @@ public:
virtual FString GetDescription() const override virtual FString GetDescription() const override
{ {
return TEXT("Remove inputs, outputs, and local variables from a graph."); return TEXT("Remove variables from a blueprint, graph, or custom event node.");
} }
virtual void Handle() override virtual void Handle() override
{ {
WingFetcher F; WingFetcher F;
UEdGraph* G = F.Walk(Graph).Cast<UEdGraph>(); UObject* Obj = F.Walk(Object).Cast<UObject>();
if (!G) return; if (!Obj) return;
WingVariables Vars(G); WingVariables Vars;
if (!Vars.SetBackingStore(Obj)) return;
if (!Vars.BlueprintVariables.ParseNamesString(BlueprintVariables)) return;
if (!Vars.InputVariables.ParseNamesString(InputVariables)) return; if (!Vars.InputVariables.ParseNamesString(InputVariables)) return;
if (!Vars.OutputVariables.ParseNamesString(OutputVariables)) return; if (!Vars.OutputVariables.ParseNamesString(OutputVariables)) return;
if (!Vars.LocalVariables.ParseNamesString(LocalVariables)) return; if (!Vars.LocalVariables.ParseNamesString(LocalVariables)) return;

View File

@@ -1,152 +0,0 @@
#include "WingBlueprintVar.h"
#include "WingServer.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "EdGraphSchema_K2.h"
#include "Kismet2/BlueprintEditorUtils.h"
FWingBlueprintVar::FWingBlueprintVar(UBlueprint* BP, const FString& VarName)
{
Desc = WingUtils::FindOneWithExternalID(VarName, BP->NewVariables, TEXT("Variable"));
if (!Desc) return;
if (Desc->VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate)
{
UWingServer::Printf(TEXT("Cannot edit event dispatchers using BlueprintVariable functions.\n"));
Desc = nullptr;
return;
}
// Try to find the default value property on the CDO.
if (BP->GeneratedClass)
{
UObject* CDO = BP->GeneratedClass->GetDefaultObject();
FProperty* Prop = BP->GeneratedClass->FindPropertyByName(Desc->VarName);
if (CDO && Prop)
DefaultValueProp = FWingProperty(Prop, CDO);
}
}
void FWingBlueprintVar::Dump()
{
LoadFlags();
LoadDefault();
TArray<FWingProperty> Props = MergedProperties();
for (FWingProperty& P : Props)
{
UWingServer::Printf(TEXT(" %s %s = %s\n"),
*UWingTypes::TypeToText(P.Prop),
*WingUtils::FormatName(P.Prop),
*P.GetText());
}
}
bool FWingBlueprintVar::ApplyJson(const FJsonObject* Json)
{
bool bHasDefault = Json->HasField(TEXT("DefaultValue"));
bool bHasType = Json->HasField(TEXT("VarType"));
if (bHasDefault && bHasType)
{
UWingServer::Print(TEXT(
"ERROR: Cannot set VarType and DefaultValue in the same call.\n"
"Change the type first, then recompile the blueprint,\n"
"then set the default.\n"));
return false;
}
LoadFlags();
TArray<FWingProperty> Props = MergedProperties();
if (!FWingProperty::PopulateFromJson(Props, Json, true))
return false;
SaveFlags();
if (bHasDefault)
return SaveDefault();
return true;
}
void FWingBlueprintVar::LoadFlags()
{
InstanceEditable = !(Desc->PropertyFlags & CPF_DisableEditOnInstance);
BlueprintReadOnly = (Desc->PropertyFlags & CPF_BlueprintReadOnly) != 0;
ExposeToCinematics = (Desc->PropertyFlags & CPF_Interp) != 0;
ExposeOnSpawn = Desc->HasMetaData(FBlueprintMetadata::MD_ExposeOnSpawn);
Private = Desc->HasMetaData(FBlueprintMetadata::MD_Private);
if (Desc->HasMetaData(TEXT("tooltip")))
Description = Desc->GetMetaData(TEXT("tooltip"));
else
Description.Empty();
}
void FWingBlueprintVar::LoadDefault()
{
if (DefaultValueProp)
DefaultValue = DefaultValueProp.GetText();
else
DefaultValue.Empty();
}
void FWingBlueprintVar::SaveFlags()
{
// CPF flags
if (InstanceEditable)
Desc->PropertyFlags &= ~CPF_DisableEditOnInstance;
else
Desc->PropertyFlags |= CPF_DisableEditOnInstance;
if (BlueprintReadOnly)
Desc->PropertyFlags |= CPF_BlueprintReadOnly;
else
Desc->PropertyFlags &= ~CPF_BlueprintReadOnly;
if (ExposeToCinematics)
Desc->PropertyFlags |= CPF_Interp;
else
Desc->PropertyFlags &= ~CPF_Interp;
// Metadata flags
if (ExposeOnSpawn)
Desc->SetMetaData(FBlueprintMetadata::MD_ExposeOnSpawn, TEXT("true"));
else
Desc->RemoveMetaData(FBlueprintMetadata::MD_ExposeOnSpawn);
if (Private)
Desc->SetMetaData(FBlueprintMetadata::MD_Private, TEXT("true"));
else
Desc->RemoveMetaData(FBlueprintMetadata::MD_Private);
// Description/tooltip
if (!Description.IsEmpty())
Desc->SetMetaData(TEXT("tooltip"), Description);
else
Desc->RemoveMetaData(TEXT("tooltip"));
}
bool FWingBlueprintVar::SaveDefault()
{
if (DefaultValueProp)
return DefaultValueProp.SetText(DefaultValue);
return true;
}
TArray<FWingProperty> FWingBlueprintVar::MergedProperties()
{
TArray<FWingProperty> Props = FWingProperty::GetAll(
FBPVariableDescription::StaticStruct(), Desc, CPF_Edit);
FWingProperty::Remove(Props, TEXT("PropertyFlags"));
FWingProperty::Remove(Props, TEXT("MetaDataArray"));
FWingProperty::Remove(Props, TEXT("VarName"));
FWingProperty::Remove(Props, TEXT("VarGuid"));
FWingProperty::Remove(Props, TEXT("DefaultValue"));
Props.Append(FWingProperty::GetAll(
FWingBlueprintVar::StaticStruct(), this, (EPropertyFlags)0));
// Remove DefaultValue if we don't have a CDO property to back it.
if (!DefaultValueProp)
FWingProperty::Remove(Props, TEXT("DefaultValue"));
return Props;
}

View File

@@ -1,132 +0,0 @@
#include "WingFunctionArgs.h"
#include "K2Node_EditablePinBase.h"
#include "K2Node_FunctionResult.h"
#include "K2Node_Tunnel.h"
#include "WingTypes.h"
#include "WingUtils.h"
#include "WingServer.h"
#include "Kismet2/BlueprintEditorUtils.h"
bool WingFunctionArgs::HasArgs(UEdGraphNode* Node)
{
UK2Node_EditablePinBase* Editable = Cast<UK2Node_EditablePinBase>(Node);
if (!Editable) return false;
return Editable->IsEditable();
}
FString WingFunctionArgs::GetArgs(UEdGraphNode* Node)
{
UK2Node_EditablePinBase* Editable = Cast<UK2Node_EditablePinBase>(Node);
if (!Editable) return FString();
TStringBuilder<256> SB;
for (const TSharedPtr<FUserPinInfo>& Pin : Editable->UserDefinedPins)
{
if (SB.Len() > 0) SB << TEXT(",");
SB << UWingTypes::TypeToText(Pin->PinType) << TEXT(" ") << WingUtils::FormatName(*Pin);
}
return FString(SB);
}
EEdGraphPinDirection WingFunctionArgs::GetPinDirection(UK2Node_EditablePinBase* Node)
{
// FunctionResult takes inputs; Tunnel depends on its flags; everything else outputs.
if (Node->IsA<UK2Node_FunctionResult>())
return EGPD_Input;
if (UK2Node_Tunnel* Tunnel = Cast<UK2Node_Tunnel>(Node))
return Tunnel->bCanHaveInputs ? EGPD_Input : EGPD_Output;
return EGPD_Output;
}
bool WingFunctionArgs::ParseArgs(const FString& Args, TArray<FParsedArg>& OutArgs)
{
FString Trimmed = Args.TrimStartAndEnd();
if (Trimmed.IsEmpty()) return true;
TArray<FString> Parts;
Trimmed.ParseIntoArray(Parts, TEXT(","));
for (const FString& Part : Parts)
{
FString Token = Part.TrimStartAndEnd();
if (Token.IsEmpty()) continue;
// Split "type name"
FString TypeStr, NameStr;
if (!Token.Split(TEXT(" "), &TypeStr, &NameStr))
{
UWingServer::Printf(TEXT("ERROR: Malformed parameter list near '%s'\n"), *Token);
return false;
}
TypeStr.TrimStartAndEndInline();
NameStr.TrimStartAndEndInline();
if (TypeStr.IsEmpty() || NameStr.IsEmpty())
{
UWingServer::Printf(TEXT("ERROR: Malformed parameter list near '%s'\n"), *Token);
return false;
}
FParsedArg Arg;
UWingTypes::Requirements Req;
Req.BlueprintType = true;
Req.Blueprintable = false;
Req.AllowContainer = true;
if (!UWingTypes::TextToType(TypeStr, Arg.PinType, Req)) return false;
Arg.PinName = FName(*NameStr);
OutArgs.Add(MoveTemp(Arg));
}
return true;
}
bool WingFunctionArgs::SetArgs(UEdGraphNode* Node, const FString& Args)
{
UK2Node_EditablePinBase* Editable = Cast<UK2Node_EditablePinBase>(Node);
if (!Editable || !Editable->IsEditable())
{
UWingServer::Printf(TEXT("ERROR: Node does not contain an editable parameter list\n"));
return false;
}
// Parse the args string.
TArray<FParsedArg> NewArgs;
if (!ParseArgs(Args, NewArgs))
{
UWingServer::SuggestManual(WingManual::Section::ParameterLists);
UWingServer::SuggestManual(WingManual::Section::Types);
return false;
}
EEdGraphPinDirection Direction = GetPinDirection(Editable);
// Replace the UserDefinedPins array directly.
Editable->UserDefinedPins.Empty();
for (const FParsedArg& Arg : NewArgs)
{
TSharedPtr<FUserPinInfo> PinInfo = MakeShareable(new FUserPinInfo());
PinInfo->PinName = Arg.PinName;
PinInfo->PinType = Arg.PinType;
PinInfo->DesiredPinDirection = Direction;
Editable->UserDefinedPins.Add(PinInfo);
}
// ReconstructNode rebuilds real pins from UserDefinedPins
// and rewires old connections by matching pin names.
Editable->ReconstructNode();
return true;
}
bool WingFunctionArgs::CheckArgs(const FString &Args)
{
TArray<FParsedArg> NewArgs;
if (!ParseArgs(Args, NewArgs))
{
UWingServer::Printf(TEXT("ERROR: Invalid parameter list: %s\n"), *Args);
UWingServer::SuggestManual(WingManual::Section::ParameterLists);
UWingServer::SuggestManual(WingManual::Section::Types);
return false;
}
return true;
}

View File

@@ -10,7 +10,6 @@
#include "EdGraphNode_Comment.h" #include "EdGraphNode_Comment.h"
#include "K2Node_CallFunction.h" #include "K2Node_CallFunction.h"
#include "K2Node_FunctionEntry.h" #include "K2Node_FunctionEntry.h"
#include "WingFunctionArgs.h"
#include "WingVariables.h" #include "WingVariables.h"
#include "MaterialGraph/MaterialGraphNode.h" #include "MaterialGraph/MaterialGraphNode.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
@@ -205,10 +204,6 @@ void WingGraphExport::EmitNode(UEdGraphNode* Node)
Output.Appendf(TEXT("\nnode %s: %s\n"), *WingUtils::FormatName(Node), *WingUtils::FormatNodeTitle(Node)); Output.Appendf(TEXT("\nnode %s: %s\n"), *WingUtils::FormatName(Node), *WingUtils::FormatNodeTitle(Node));
// Emit function args (if applicable).
if (WingFunctionArgs::HasArgs(Node))
Output.Appendf(TEXT(" args %s\n"), *WingFunctionArgs::GetArgs(Node));
// Emit material expression properties (if applicable). // Emit material expression properties (if applicable).
EmitMaterialProperties(Node, Output, true); EmitMaterialProperties(Node, Output, true);
@@ -289,7 +284,8 @@ void WingGraphExport::EmitMaterialProperties(UEdGraphNode* Node, FStringBuilderB
void WingGraphExport::EmitLocalVariables() void WingGraphExport::EmitLocalVariables()
{ {
WingVariables Vars(Graph); WingVariables Vars;
Vars.SetBackingStore(Graph);
Vars.Load(); Vars.Load();
Vars.Print(Output); Vars.Print(Output);
} }

View File

@@ -7,6 +7,7 @@
#include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2.h"
#include "K2Node_FunctionEntry.h" #include "K2Node_FunctionEntry.h"
#include "K2Node_FunctionResult.h" #include "K2Node_FunctionResult.h"
#include "K2Node_CustomEvent.h"
#include "K2Node_Tunnel.h" #include "K2Node_Tunnel.h"
#include "K2Node_EditablePinBase.h" #include "K2Node_EditablePinBase.h"
#include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/BlueprintEditorUtils.h"
@@ -59,6 +60,17 @@ void WingVariableList::Print(FStringBuilderBase &Out)
} }
} }
void WingVariableList::PrintCompact(FStringBuilderBase &Out)
{
bool First = true;
for (const Var& V : Variables)
{
if (!First) Out << TEXT(",");
First = false;
Out << UWingTypes::TypeToText(V.Type) << TEXT(" ") << WingUtils::ExternalizeID(V.Name);
}
}
void WingVariableList::ClearLinks() void WingVariableList::ClearLinks()
{ {
for (Var &V : Variables) for (Var &V : Variables)
@@ -241,6 +253,8 @@ void WingVariables::Load()
Empty(); Empty();
if (Blueprint != nullptr) return LoadBlueprint(); if (Blueprint != nullptr) return LoadBlueprint();
if (Graph != nullptr) return LoadGraph(); if (Graph != nullptr) return LoadGraph();
if (CustomEvent != nullptr) return LoadCustomEvent();
ErrorNoBackingStore();
} }
void WingVariables::LoadBlueprint() void WingVariables::LoadBlueprint()
@@ -337,12 +351,17 @@ void WingVariables::LoadEditablePinBase(UK2Node_EditablePinBase* Node, WingVaria
} }
} }
void WingVariables::LoadCustomEvent()
{
LoadEditablePinBase(CustomEvent, InputVariables);
}
bool WingVariables::Check() bool WingVariables::Check()
{ {
if (Blueprint) return CheckBlueprint(); if (Blueprint) return CheckBlueprint();
if (Graph) return CheckGraph(); if (Graph) return CheckGraph();
check(false); if (CustomEvent) return CheckCustomEvent();
return false; return ErrorNoBackingStore();
} }
bool WingVariables::CheckBlueprint() bool WingVariables::CheckBlueprint()
@@ -363,7 +382,7 @@ bool WingVariables::CheckGraph()
bool AllowOutput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro); bool AllowOutput = (T == EGraphType::GT_Function) || (T == EGraphType::GT_Macro);
if ((!AllowLocal) && (!AllowInput) && (!AllowOutput)) if ((!AllowLocal) && (!AllowInput) && (!AllowOutput))
{ {
UWingServer::Printf(TEXT("This graph has no editable variables.")); UWingServer::Printf(TEXT("This graph type has no variables."));
return false; return false;
} }
@@ -375,12 +394,22 @@ bool WingVariables::CheckGraph()
return OK; return OK;
} }
bool WingVariables::CheckCustomEvent()
{
bool OK = true;
if (!BlueprintVariables.CheckSanity(Flags_None, false)) OK = false;
if (!LocalVariables.CheckSanity(Flags_None, false)) OK = false;
if (!InputVariables.CheckSanity(Flags_None, true)) OK = false;
if (!OutputVariables.CheckSanity(Flags_None, false)) OK = false;
return OK;
}
bool WingVariables::Modify() bool WingVariables::Modify()
{ {
if (Blueprint) return ModifyBlueprint(); if (Blueprint) return ModifyBlueprint();
if (Graph) return ModifyGraph(); if (Graph) return ModifyGraph();
check(false); if (CustomEvent) return ModifyCustomEvent();
return false; return ErrorNoBackingStore();
} }
bool WingVariables::ModifyBlueprint() bool WingVariables::ModifyBlueprint()
@@ -488,28 +517,36 @@ bool WingVariables::ModifyGraph()
Desc->VarType = V.Type; Desc->VarType = V.Type;
if (V.DefaultSpecified) Desc->DefaultValue = V.DefaultValue; if (V.DefaultSpecified) Desc->DefaultValue = V.DefaultValue;
} }
for (Var &V : InputVariables.Variables) if (!ModifyEditablePinBase(InputVariables, InputNode)) return false;
{ if (!ModifyEditablePinBase(OutputVariables, OutputNode)) return false;
TSharedPtr<FUserPinInfo> *Found =
WingUtils::FindOneWithInternalID(V.Name, InputNode->UserDefinedPins, TEXT("input variable"));
if (!Found) return false;
(*Found)->PinType = V.Type;
if (V.DefaultSpecified) (*Found)->PinDefaultValue = V.DefaultValue;
}
for (Var &V : OutputVariables.Variables)
{
TSharedPtr<FUserPinInfo> *Found =
WingUtils::FindOneWithInternalID(V.Name, OutputNode->UserDefinedPins, TEXT("output variable"));
if (!Found) return false;
(*Found)->PinType = V.Type;
if (V.DefaultSpecified) (*Found)->PinDefaultValue = V.DefaultValue;
}
if (InputNode) InputNode->ReconstructNode(); if (InputNode) InputNode->ReconstructNode();
if (OutputNode) OutputNode->ReconstructNode(); if (OutputNode) OutputNode->ReconstructNode();
return true; return true;
} }
bool WingVariables::ModifyCustomEvent()
{
if (!CheckCustomEvent()) return false;
ClearLinks();
if (!ModifyEditablePinBase(InputVariables, CustomEvent)) return false;
CustomEvent->ReconstructNode();
return true;
}
bool WingVariables::ModifyEditablePinBase(WingVariableList &List, UK2Node_EditablePinBase *Node)
{
for (Var &V : List.Variables)
{
TSharedPtr<FUserPinInfo> *Found =
WingUtils::FindOneWithInternalID(V.Name, Node->UserDefinedPins, List.ListName);
if (!Found) return false;
(*Found)->PinType = V.Type;
if (V.DefaultSpecified) (*Found)->PinDefaultValue = V.DefaultValue;
}
return true;
}
bool WingVariables::GetGraphNodes( bool WingVariables::GetGraphNodes(
UK2Node_EditablePinBase *&InputNode, UK2Node_EditablePinBase *&InputNode,
UK2Node_EditablePinBase *&OutputNode, UK2Node_EditablePinBase *&OutputNode,
@@ -554,8 +591,8 @@ bool WingVariables::Create()
{ {
if (Blueprint) return CreateBlueprint(); if (Blueprint) return CreateBlueprint();
if (Graph) return CreateGraph(); if (Graph) return CreateGraph();
check(false); if (CustomEvent) return CreateCustomEvent();
return false; return ErrorNoBackingStore();
} }
bool WingVariables::CreateBlueprint() bool WingVariables::CreateBlueprint()
@@ -635,6 +672,25 @@ bool WingVariables::CreateGraph()
return true; return true;
} }
bool WingVariables::CreateCustomEvent()
{
if (!CheckCustomEvent()) return false;
ClearLinks();
// Check for name collisions against existing pins.
TSet<FName> Names;
if (!WingUtils::FindNoDuplicateNames(
Names, CustomEvent->UserDefinedPins, TEXT("event parameter"))) return false;
if (!WingUtils::FindNoDuplicateNames(
Names, InputVariables.Variables, TEXT("event parameter"))) return false;
for (const Var& V : InputVariables.Variables)
AddUserPinInfo(V, EGPD_Output, CustomEvent);
CustomEvent->ReconstructNode();
return true;
}
void WingVariables::AddUserPinInfo(const Var &V, EEdGraphPinDirection Dir, UK2Node_EditablePinBase *Node) void WingVariables::AddUserPinInfo(const Var &V, EEdGraphPinDirection Dir, UK2Node_EditablePinBase *Node)
{ {
TSharedPtr<FUserPinInfo> Result = MakeShareable( new FUserPinInfo() ); TSharedPtr<FUserPinInfo> Result = MakeShareable( new FUserPinInfo() );
@@ -649,8 +705,8 @@ bool WingVariables::Remove()
{ {
if (Blueprint) return RemoveBlueprint(); if (Blueprint) return RemoveBlueprint();
if (Graph) return RemoveGraph(); if (Graph) return RemoveGraph();
check(false); if (CustomEvent) return RemoveCustomEvent();
return false; return ErrorNoBackingStore();
} }
bool WingVariables::RemoveBlueprint() bool WingVariables::RemoveBlueprint()
@@ -715,3 +771,55 @@ bool WingVariables::RemoveGraph()
if (OutputNode) OutputNode->ReconstructNode(); if (OutputNode) OutputNode->ReconstructNode();
return true; return true;
} }
bool WingVariables::RemoveCustomEvent()
{
// Verify that all named parameters exist before removing anything.
for (const Var& V : InputVariables.Variables)
{
TSharedPtr<FUserPinInfo>* Found =
WingUtils::FindOneWithInternalID(V.Name, CustomEvent->UserDefinedPins, TEXT("event parameter"));
if (!Found) return false;
}
for (const Var& V : InputVariables.Variables)
CustomEvent->RemoveUserDefinedPinByName(V.Name);
CustomEvent->ReconstructNode();
return true;
}
bool WingVariables::SetBackingStore(UObject *Obj)
{
Blueprint = nullptr;
Graph = nullptr;
CustomEvent = nullptr;
if (UBlueprint *BP = Cast<UBlueprint>(Obj))
{
Blueprint = BP;
return true;
}
if (UEdGraph *G = Cast<UEdGraph>(Obj))
{
Graph = G;
return true;
}
if (UK2Node_CustomEvent *E = Cast<UK2Node_CustomEvent>(Obj))
{
CustomEvent = E;
return true;
}
UWingServer::Printf(TEXT(
"ERROR: The variable editor can only edit blueprints, "
"graphs, and custom event nodes. Passed in: %s"),
*WingUtils::FormatName(Obj->GetClass()));
return false;
}
bool WingVariables::ErrorNoBackingStore()
{
UWingServer::Printf(TEXT(
"ERROR: The variable editor was not successfully "
"set up with an object to edit."));
return false;
}

View File

@@ -1,58 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "WingProperty.h"
#include "Engine/Blueprint.h"
#include "WingBlueprintVar.generated.h"
// Editor-friendly view of a blueprint variable's properties.
// Wraps an FBPVariableDescription, exposing commonly-used flags
// and metadata as simple UPROPERTYs that the property system can
// populate from JSON.
USTRUCT()
struct FWingBlueprintVar
{
GENERATED_BODY()
FBPVariableDescription* Desc = nullptr;
FWingProperty DefaultValueProp;
FWingBlueprintVar() = default;
FWingBlueprintVar(UBlueprint* BP, const FString& VarName);
bool NotFound() const { return Desc == nullptr; }
UPROPERTY(EditAnywhere, meta=(Optional, Description="Default value in Unreal text format"))
FString DefaultValue;
UPROPERTY(EditAnywhere, meta=(Optional, Description="Variable description/tooltip"))
FString Description;
UPROPERTY(EditAnywhere, meta=(Optional, Description="Allow editing on instances"))
bool InstanceEditable = false;
UPROPERTY(EditAnywhere, meta=(Optional, Description="Read-only in blueprints"))
bool BlueprintReadOnly = false;
UPROPERTY(EditAnywhere, meta=(Optional, Description="Expose as a pin when spawning"))
bool ExposeOnSpawn = false;
UPROPERTY(EditAnywhere, meta=(Optional, Description="Private to this blueprint"))
bool Private = false;
UPROPERTY(EditAnywhere, meta=(Optional, Description="Expose to cinematics/sequencer"))
bool ExposeToCinematics = false;
// Load from Desc, populate from JSON, save back to Desc.
bool ApplyJson(const FJsonObject* Json);
// Print all properties and their current values.
void Dump();
private:
void LoadFlags();
void LoadDefault();
void SaveFlags();
bool SaveDefault();
TArray<FWingProperty> MergedProperties();
};

View File

@@ -1,40 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "EdGraph/EdGraphPin.h"
class UEdGraphNode;
class UK2Node_EditablePinBase;
struct WingFunctionArgs
{
// Returns true if the node is an EditablePinBase subclass that
// actually supports user-defined pins (FunctionEntry, FunctionResult,
// CustomEvent, or Tunnel).
static bool HasArgs(UEdGraphNode* Node);
// Returns the user-defined pins as a string like "int x,float y".
static FString GetArgs(UEdGraphNode* Node);
// Sets the user-defined pins from a string like "int x,float y".
// Returns true on success.
static bool SetArgs(UEdGraphNode* Node, const FString& Args);
// Returns true if the arguments are valid, if not prints error.
static bool CheckArgs(const FString &Args);
private:
// A parsed argument: type + name.
struct FParsedArg
{
FEdGraphPinType PinType;
FName PinName;
};
// Parse "int x,float y" into an array of FParsedArg.
// Returns false and prints an error on failure.
static bool ParseArgs(const FString& Args, TArray<FParsedArg>& OutArgs);
// Determine the pin direction for user-defined pins on this node.
static EEdGraphPinDirection GetPinDirection(UK2Node_EditablePinBase* Node);
};

View File

@@ -5,6 +5,7 @@
#include "Engine/Blueprint.h" #include "Engine/Blueprint.h"
struct WingTokenizer; struct WingTokenizer;
class UK2Node_CustomEvent;
class UK2Node_EditablePinBase; class UK2Node_EditablePinBase;
class UK2Node_FunctionEntry; class UK2Node_FunctionEntry;
struct FUserPinInfo; struct FUserPinInfo;
@@ -55,6 +56,9 @@ public:
// Print the variables to a string builder. // Print the variables to a string builder.
void Print(FStringBuilderBase &Out); void Print(FStringBuilderBase &Out);
// Print compact: "type name,type name,..."
void PrintCompact(FStringBuilderBase &Out);
// Clear the BPVar and Pin fields. // Clear the BPVar and Pin fields.
void ClearLinks(); void ClearLinks();
@@ -77,11 +81,13 @@ class WingVariables
{ {
public: public:
using Var = WingVariableList::Var; using Var = WingVariableList::Var;
WingVariables() {}
// The backing store. Only one of these should be set. // The backing store. Only one of these should be set.
UBlueprint *Blueprint = nullptr; UBlueprint *Blueprint = nullptr;
UEdGraph *Graph = nullptr; UEdGraph *Graph = nullptr;
UK2Node_CustomEvent *CustomEvent = nullptr;
// The Workspace. At any given time, these may or may not contain // The Workspace. At any given time, these may or may not contain
// the same data as the backing store. // the same data as the backing store.
@@ -91,10 +97,10 @@ public:
WingVariableList InputVariables{TEXT("Input Variables")}; WingVariableList InputVariables{TEXT("Input Variables")};
WingVariableList OutputVariables{TEXT("Output Variables")}; WingVariableList OutputVariables{TEXT("Output Variables")};
// Constructors. Just initialize the pointers to the backing store. // Configure the backing store. On failure,
// prints a message and returns false.
WingVariables(UBlueprint *BP) : Blueprint(BP) {} bool SetBackingStore(UObject *Obj);
WingVariables(UEdGraph *G) : Graph(G) {}
// Clear the workspace. Doesn't affect the backing store. // Clear the workspace. Doesn't affect the backing store.
@@ -155,11 +161,20 @@ private:
bool RemoveBlueprint(); bool RemoveBlueprint();
bool RemoveGraph(); bool RemoveGraph();
void LoadCustomEvent();
bool CheckCustomEvent();
bool ModifyCustomEvent();
bool CreateCustomEvent();
bool RemoveCustomEvent();
bool GetGraphNodes( bool GetGraphNodes(
UK2Node_EditablePinBase *&InputNode, UK2Node_EditablePinBase *&InputNode,
UK2Node_EditablePinBase *&OutputNode, UK2Node_EditablePinBase *&OutputNode,
UK2Node_FunctionEntry *&LocalNode); UK2Node_FunctionEntry *&LocalNode);
bool ModifyEditablePinBase(WingVariableList &List, UK2Node_EditablePinBase *Node);
void AddUserPinInfo(const Var &V, EEdGraphPinDirection Dir, UK2Node_EditablePinBase *Node); void AddUserPinInfo(const Var &V, EEdGraphPinDirection Dir, UK2Node_EditablePinBase *Node);
bool ErrorNoBackingStore();
}; };

View File

@@ -2,7 +2,8 @@
""" """
Human-friendly MCP test client. Human-friendly MCP test client.
Usage: ue-wingman.py UserManual Usage: ue-wingman.py <command> [key=value ...]
ue-wingman.py (reads JSON with "command" from stdin)
""" """
import sys import sys
@@ -17,27 +18,28 @@ TIMEOUT = 120
def main(): def main():
args = sys.argv[1:] args = sys.argv[1:]
if not args: if not args:
print("Usage: ue-wingman.py ShowCommands [key=value ...]") # No arguments: read a complete JSON object from stdin.
sys.exit(1) # The JSON must already contain a "command" key.
no_args_commands = {"ShowCommands", "UserManual"}
if len(args) == 1 and args[0] not in no_args_commands:
# No extra arguments: read JSON object from stdin.
# Accumulate lines and try to parse after each one.
decoder = json.JSONDecoder() decoder = json.JSONDecoder()
raw = "" raw = ""
msg = None msg = None
for line in sys.stdin: for line in sys.stdin:
raw += line raw += line
stripped = raw.strip()
try: try:
msg, _ = decoder.raw_decode(raw.lstrip()) msg, _ = decoder.raw_decode(stripped)
break break
except json.JSONDecodeError: except json.JSONDecodeError as e:
if e.pos < len(stripped):
print(f"Malformed JSON: {e.msg}")
sys.exit(1)
continue continue
if msg is None: if msg is None:
print("Could not parse a complete JSON object from stdin") print("Could not parse a complete JSON object from stdin")
sys.exit(1) sys.exit(1)
msg["command"] = args[0] if "command" not in msg:
print("JSON object must contain a \"command\" key")
sys.exit(1)
else: else:
msg = {"command": args[0]} msg = {"command": args[0]}
for arg in args[1:]: for arg in args[1:]: