Stricter name validation, and better disambiguation of graph actions

This commit is contained in:
2026-04-09 22:01:19 -04:00
parent 46ac6b34a8
commit 224e6604e6
13 changed files with 153 additions and 63 deletions

View File

@@ -13,8 +13,8 @@ The luprex DLL never calls into the Unreal driver. Output goes through polled bu
- Use `build.py` for all builds. Do NOT follow Epic's standard build instructions. - Use `build.py` for all builds. Do NOT follow Epic's standard build instructions.
- `build.py all` — full rebuild (engine, game, intellisense, project files) - `build.py all` — full rebuild (engine, game, intellisense, project files)
- `build.py c++` — lightweight rebuild (only if you've only edited C++ files in this repo) - `build.py c++` — lightweight rebuild (use if you've only edited c++ files)
- Lua and Blueprint edits don't require a rebuild. - Lua and Blueprint edits don't require any kind of build.
## Directory Structure ## Directory Structure
@@ -24,6 +24,8 @@ The luprex DLL never calls into the Unreal driver. Output goes through polled bu
- `Docs/` — Documentation. When trying to understand this system, start with the markdown files in the Docs directory. - `Docs/` — Documentation. When trying to understand this system, start with the markdown files in the Docs directory.
- `Config/` — Unreal config files - `Config/` — Unreal config files
- `EnginePatches/` — Custom engine modifications - `EnginePatches/` — Custom engine modifications
- `Plugins/UEWingman/' - An MCP that gives you control over the unreal editor.
- `../integration.UE/` - the unreal engine source tree
## Architecture: World Models and Predictive Reexecution ## Architecture: World Models and Predictive Reexecution
@@ -46,11 +48,6 @@ See `Docs/Predictive-Reexecution.md` for the full explanation.
Lua scripts have no access to the Unreal API whatsoever. The scripter works with plain Lua tables, animation queues of key-value tuples, and coroutines. There are no "unreal bindings." The Luprex DLL is engine-agnostic — Unreal (or any other front end) interprets the animation queues and renders accordingly. Lua scripts have no access to the Unreal API whatsoever. The scripter works with plain Lua tables, animation queues of key-value tuples, and coroutines. There are no "unreal bindings." The Luprex DLL is engine-agnostic — Unreal (or any other front end) interprets the animation queues and renders accordingly.
Small concessions to reality exist in animation step tuples:
- `bp` — names which Tangible Actor blueprint to use for rendering
- `mat_color` — a persistent value that hints at material color
These are still just key-value data; the script never touches the Unreal API directly.
## Architecture: Tangibles ## Architecture: Tangibles
Tangibles are game objects. Each has: Tangibles are game objects. Each has:
@@ -70,10 +67,6 @@ On the Unreal side, **Tangible Actor blueprints** (TangibleStaticMesh, TangibleS
- **LuaDefine macro** — declares Lua-callable C++ functions and auto-registers them in a global registry for automatic insertion into the Lua environment. - **LuaDefine macro** — declares Lua-callable C++ functions and auto-registers them in a global registry for automatic insertion into the Lua environment.
- **eng::malloc heap** — custom deterministic memory allocator for the driven portion, ensuring reproducible addresses during replay. - **eng::malloc heap** — custom deterministic memory allocator for the driven portion, ensuring reproducible addresses during replay.
## Architecture: Determinism
The driven portion must be fully deterministic so that synchronous models stay in lockstep and event replay works. Rules: no true random numbers, no iterating unordered maps, no real-time clocks, no threads (with carefully sandboxed exceptions). See `Docs/The-Event-Driven-Structure-of-the-Engine.md`.
## Architecture: GUI System ## Architecture: GUI System
Blueprints call into Lua via two mechanisms: Blueprints call into Lua via two mechanisms:
@@ -82,39 +75,22 @@ Blueprints call into Lua via two mechanisms:
Look-at widgets, hotkeys, and menus are built on top of this. The menu system is implemented entirely in "user space" Lua and blueprint code. See `Docs/Displaying-Widget-Blueprints.md`. Look-at widgets, hotkeys, and menus are built on top of this. The menu system is implemented entirely in "user space" Lua and blueprint code. See `Docs/Displaying-Widget-Blueprints.md`.
## Blueprint Text Export
Blueprints are automatically exported to readable text files in `Saved/BlueprintExports/` whenever they are saved in the editor. This lets Claude Code read blueprint logic. See `Docs/Blueprint Text Export.md` for format details. Source: `Source/Integration/BlueprintExporter.h/.cpp` and `Source/Integration/Integration.cpp`.
## Key Documentation ## Key Documentation
Look in the Docs directory for important documentation. Look in the Docs directory for important documentation.
## Git
Do not use git to make changes (commit, push, branch, etc.). Read-only git commands (status, log, diff, etc.) are fine.
## Workflow ## Workflow
- When the user gives a direct command, execute it. But when proposing changes on your own initiative, describe the plan and get approval before editing files. - Do not use git to make changes (commit, push, branch, etc.). Read-only git commands (status, log, diff, etc.) are fine.
- Work at the user's pace. Do not start coding until the user says it is time.
- If an instruction ends with an ellipsis (`...`), the user has more to say. Wait for the next message before acting. - If an instruction ends with an ellipsis (`...`), the user has more to say. Wait for the next message before acting.
- Do not output multiple paragraphs. Doing so is very rude. You are having a conversation, give the other person a
chance to speak. For most questions, 3-4 sentences is the maximum, unless you've been asked to give a
detailed explanation.
## Coding Conventions ## Coding Conventions
- Prefer early returns and `continue` to reduce nesting (never-nester style). - Prefer early returns and `continue` to reduce nesting (never-nester style).
- Do not use static functions in Unreal code. Use class methods or namespace-scoped functions instead. - Do not use static functions in Unreal code. Use class methods instead.
- Use `LogLuprexIntegration` for log messages, not `LogTemp`. - Use `LogLuprexIntegration` for log messages inside Source/. Use LogTemp inside Plugins/.
- When writing UFUNCTIONs that take an `AActor*`, `UObject*`, or similar "self" parameter, add `DefaultToSelf` meta to that pin. Most functions should have this on the obvious pin so the user doesn't have to manually wire it in blueprints.
## Session Startup
At the beginning of every session, do a directory listing of these three directories so you know what files are available:
- `Docs/` — documentation
- `Source/Integration/` — Unreal-side C++ code
- `luprex/cpp/core/` — Luprex DLL core C++ code
These two code directories contain 99% of the code we'll be working on together.
## Current Status
(Use this section to track what we're working on across sessions.)

View File

@@ -1 +0,0 @@
AGENTS.md

View File

@@ -1,5 +1,7 @@
* UE Wingman Widgets: cannot edit 'Is Variable' flag or widget name. * UE Wingman Widgets: cannot edit 'Is Variable' flag or widget name.
TSharedPtr<INameValidatorInterface> NameValidator = MakeShareable(new FKismetNameValidator(Blueprint, OldObjectName));
* Keyboard Event Handling * Keyboard Event Handling
* Menus * Menus

View File

@@ -51,7 +51,7 @@ public:
FName InternalID = WingUtils::CheckProposedName(Component, WingOut::Stdout); FName InternalID = WingUtils::CheckProposedName(Component, WingOut::Stdout);
if (InternalID.IsNone()) return; if (InternalID.IsNone()) return;
TSet<FName> Names; TSet<FName> Names;
FBlueprintEditorUtils::GetClassVariableList(BP, Names); WingUtils::GetAllInUseNames(BP, Names);
if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("variable or component"), WingOut::Stdout)) return; if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("variable or component"), WingOut::Stdout)) return;
// Resolve the component class by name // Resolve the component class by name

View File

@@ -46,7 +46,7 @@ public:
FName InternalID = WingUtils::CheckProposedName(Dispatcher, WingOut::Stdout); FName InternalID = WingUtils::CheckProposedName(Dispatcher, WingOut::Stdout);
if (InternalID.IsNone()) return; if (InternalID.IsNone()) return;
TSet<FName> Names; TSet<FName> Names;
FBlueprintEditorUtils::GetClassVariableList(BP, Names); WingUtils::GetAllInUseNames(BP, Names);
if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("variable or component"), WingOut::Stdout)) return; if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("variable or component"), WingOut::Stdout)) return;
// Parse the arguments. // Parse the arguments.

View File

@@ -51,7 +51,6 @@ public:
WingFetcher F(WingOut::Stdout); WingFetcher F(WingOut::Stdout);
UWidgetBlueprint* BP = F.Walk(Blueprint).Cast<UWidgetBlueprint>(); UWidgetBlueprint* BP = F.Walk(Blueprint).Cast<UWidgetBlueprint>();
if (!BP) return; if (!BP) return;
UWidgetTree* Tree = BP->WidgetTree;
// Resolve the widget type. // Resolve the widget type.
WingWidgets WidgetMenu; WingWidgets WidgetMenu;
@@ -71,15 +70,14 @@ public:
if (InternalID.IsNone()) return; if (InternalID.IsNone()) return;
// Check that the name is unique among existing widgets. // Check that the name is unique among existing widgets.
TArray<UWidget*> AllWidgets;
Tree->GetAllWidgets(AllWidgets);
TSet<FName> Names; TSet<FName> Names;
FBlueprintEditorUtils::GetClassVariableList(BP, Names); WingUtils::GetAllInUseNames(BP, Names);
if (!WingUtils::FindNoDuplicateNames(Names, AllWidgets, TEXT("widget or variable"), WingOut::Stdout)) return;
if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("widget or variable"), WingOut::Stdout)) return; if (!WingUtils::FindNoDuplicateName(Names, InternalID, TEXT("widget or variable"), WingOut::Stdout)) return;
// If a parent is specified, find it and verify it's a panel. // If a parent is specified, find it and verify it's a panel.
UPanelWidget* ParentPanel = nullptr; UPanelWidget* ParentPanel = nullptr;
TArray<UWidget*> AllWidgets;
BP->WidgetTree->GetAllWidgets(AllWidgets);
if (!Parent.IsEmpty()) if (!Parent.IsEmpty())
{ {
UWidget* ParentWidget = WingUtils::FindOneWithExternalID(Parent, AllWidgets, TEXT("Widget"), WingOut::Stdout); UWidget* ParentWidget = WingUtils::FindOneWithExternalID(Parent, AllWidgets, TEXT("Widget"), WingOut::Stdout);
@@ -89,7 +87,7 @@ public:
} }
else else
{ {
if (Tree->RootWidget != nullptr) if (BP->WidgetTree->RootWidget != nullptr)
{ {
WingOut::Stdout.Printf(TEXT("ERROR: Widget tree already has a root widget. Specify a Parent.\n")); WingOut::Stdout.Printf(TEXT("ERROR: Widget tree already has a root widget. Specify a Parent.\n"));
return; return;
@@ -99,9 +97,9 @@ public:
// Create the widget. // Create the widget.
UWidget* NewWidget; UWidget* NewWidget;
if (WidgetClass->IsChildOf(UUserWidget::StaticClass())) if (WidgetClass->IsChildOf(UUserWidget::StaticClass()))
NewWidget = CreateWidget<UUserWidget>(Tree, WidgetClass, InternalID); NewWidget = CreateWidget<UUserWidget>(BP->WidgetTree, WidgetClass, InternalID);
else else
NewWidget = NewObject<UWidget>(Tree, WidgetClass, InternalID, RF_Transactional); NewWidget = NewObject<UWidget>(BP->WidgetTree, WidgetClass, InternalID, RF_Transactional);
if (!NewWidget) if (!NewWidget)
{ {
@@ -120,7 +118,7 @@ public:
} }
else else
{ {
Tree->RootWidget = NewWidget; BP->WidgetTree->RootWidget = NewWidget;
} }
WingOut::Stdout.Printf(TEXT("Created widget '%s' of type '%s'\n"), *Name, *Type); WingOut::Stdout.Printf(TEXT("Created widget '%s' of type '%s'\n"), *Name, *Type);

View File

@@ -150,7 +150,7 @@ bool UWingComponent::AddComponent(UBlueprint *BP, UClass *Class, UWingComponentR
return false; return false;
} }
TSet<FName> Names; TSet<FName> Names;
FBlueprintEditorUtils::GetClassVariableList(BP, Names); WingUtils::GetAllInUseNames(BP, Names);
if (Names.Contains(Name)) if (Names.Contains(Name))
{ {
Errors.Printf(TEXT("There is already a variable or component named %s in %s.\n"), Errors.Printf(TEXT("There is already a variable or component named %s in %s.\n"),

View File

@@ -1,10 +1,75 @@
#include "WingGraphActions.h" #include "WingGraphActions.h"
#include "EdGraph/EdGraphSchema.h" #include "AnimNotifyEventNodeSpawner.h"
#include "BlueprintActionFilter.h"
#include "BlueprintActionDatabase.h" #include "BlueprintActionDatabase.h"
#include "BlueprintBoundEventNodeSpawner.h"
#include "BlueprintComponentNodeSpawner.h"
#include "BlueprintDelegateNodeSpawner.h"
#include "BlueprintEventNodeSpawner.h"
#include "BlueprintFunctionNodeSpawner.h"
#include "BlueprintNodeSpawner.h" #include "BlueprintNodeSpawner.h"
#include "BlueprintVariableNodeSpawner.h"
#include "EdGraph/EdGraphSchema.h"
#include "EdGraphSchema_K2.h" #include "EdGraphSchema_K2.h"
#include "Animation/AnimInstance.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "WingUtils.h" #include "WingUtils.h"
FName FWingGraphAction::GetPropertyClassName(const FProperty *Prop)
{
if (Prop == nullptr) return FName();
UClass *Class = Prop->GetOwnerClass();
if (Class == nullptr) return FName();
return Class->GetFName();
}
FName FWingGraphAction::GetSpawnerOwnerClass(const UBlueprintNodeSpawner* Spawner)
{
if (const UBlueprintFunctionNodeSpawner* FunctionSpawner = Cast<UBlueprintFunctionNodeSpawner>(Spawner))
{
return FunctionSpawner->GetFunction()->GetOwnerClass()->GetFName();
}
if (const UBlueprintVariableNodeSpawner* VariableSpawner = Cast<UBlueprintVariableNodeSpawner>(Spawner))
{
return GetPropertyClassName(VariableSpawner->GetVarProperty());
}
if (const UBlueprintDelegateNodeSpawner* DelegateSpawner = Cast<UBlueprintDelegateNodeSpawner>(Spawner))
{
return GetPropertyClassName(DelegateSpawner->GetDelegateProperty());
}
if (const UBlueprintBoundEventNodeSpawner* BoundEventSpawner = Cast<UBlueprintBoundEventNodeSpawner>(Spawner))
{
return GetPropertyClassName(BoundEventSpawner->GetEventDelegate());
}
if (Cast<UAnimNotifyEventNodeSpawner>(Spawner))
{
return UAnimInstance::StaticClass()->GetFName();
}
if (const UBlueprintEventNodeSpawner* EventSpawner = Cast<UBlueprintEventNodeSpawner>(Spawner))
{
if (EventSpawner->IsForCustomEvent()) return FName();
return EventSpawner->GetEventFunction()->GetOwnerClass()->GetFName();
}
if (const UBlueprintComponentNodeSpawner* ComponentSpawner = Cast<UBlueprintComponentNodeSpawner>(Spawner))
{
if (const UClass* ComponentClass = ComponentSpawner->GetComponentClass())
{
return ComponentClass->GetFName();
}
// todo: get a class name from the asset data.
return FName();
}
return FName();
}
FWingGraphAction::FWingGraphAction(TSharedPtr<FEdGraphSchemaAction> &iAction, UEdGraph *iGraph) FWingGraphAction::FWingGraphAction(TSharedPtr<FEdGraphSchemaAction> &iAction, UEdGraph *iGraph)
{ {
Action = iAction; Action = iAction;
@@ -20,10 +85,13 @@ FWingGraphAction::FWingGraphAction(UBlueprintNodeSpawner *iSpawner, UEdGraph *iG
Spawner = iSpawner; Spawner = iSpawner;
Graph = iGraph; Graph = iGraph;
const FBlueprintActionUiSpec& UiSpec = Spawner->PrimeDefaultUiSpec(); const FBlueprintActionUiSpec& UiSpec = Spawner->PrimeDefaultUiSpec();
FString Category = UiSpec.Category.ToString(); Name = UiSpec.Category.ToString() + TEXT("|") + UiSpec.MenuName.ToString();
FString MenuName = UiSpec.MenuName.ToString(); Keywords = UiSpec.Keywords.ToString();
Name = WingUtils::StandardizeMenuItem(Category + TEXT("|") + MenuName); if (FName OwnerClass = GetSpawnerOwnerClass(Spawner); !OwnerClass.IsNone())
Keywords = Spawner->PrimeDefaultUiSpec().Keywords.ToString(); {
Name = OwnerClass.ToString() + TEXT("|") + Name;
}
Name = WingUtils::StandardizeMenuItem(Name);
} }
UEdGraphNode* FWingGraphAction::Execute(const FVector2D &Location) const UEdGraphNode* FWingGraphAction::Execute(const FVector2D &Location) const
@@ -72,21 +140,31 @@ void FWingGraphActions::CollectActions()
void FWingGraphActions::CollectSpawners() void FWingGraphActions::CollectSpawners()
{ {
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph);
if (!Blueprint) return;
FBlueprintActionContext Context;
Context.Blueprints.Add(Blueprint);
Context.Graphs.Add(Graph);
FBlueprintActionFilter Filter(FBlueprintActionFilter::BPFILTER_NoFlags);
Filter.Context = Context;
for (const auto& Pair : FBlueprintActionDatabase::Get().GetAllActions()) for (const auto& Pair : FBlueprintActionDatabase::Get().GetAllActions())
{ {
UObject* ActionObject = Pair.Key.ResolveObjectPtr();
if (!ActionObject) continue;
for (UBlueprintNodeSpawner* Spawner : Pair.Value) for (UBlueprintNodeSpawner* Spawner : Pair.Value)
{ {
if (Spawner->NodeClass) if (!Spawner->NodeClass) continue;
{
UEdGraphNode* NodeCDO = CastChecked<UEdGraphNode>(Spawner->NodeClass->ClassDefaultObject); FBlueprintActionInfo ActionInfo(ActionObject, Spawner);
if (NodeCDO->IsCompatibleWithGraph(Graph)) if (Filter.IsFiltered(ActionInfo)) continue;
{
Actions.Emplace(Spawner, Graph); Actions.Emplace(Spawner, Graph);
} }
} }
} }
}
}
FWingGraphActions::FWingGraphActions(UEdGraph *iGraph) FWingGraphActions::FWingGraphActions(UEdGraph *iGraph)
{ {

View File

@@ -474,7 +474,8 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, bool Mutable)
} }
// If it's a Widget, hide the slot property, and add the // If it's a Widget, hide the slot property, and add the
// slot properties. // slot properties. Then, add the 'bIsVariable' property and
// make it editable.
if (UWidget* Widget = Cast<UWidget>(Obj)) if (UWidget* Widget = Cast<UWidget>(Obj))
{ {
FWingProperty::Remove(Result, TEXT("Slot")); FWingProperty::Remove(Result, TEXT("Slot"));
@@ -482,6 +483,8 @@ TArray<FWingProperty> FWingProperty::GetDetails(UObject* Obj, bool Mutable)
{ {
Result.Append(GetVisible(Slot)); Result.Append(GetVisible(Slot));
} }
FProperty *VarProp = Widget->GetClass()->FindPropertyByName(TEXT("bIsVariable"));
if (VarProp) Result.Add(FWingProperty(VarProp, Widget, true));
} }
if (!Mutable) StripEditable(Result); if (!Mutable) StripEditable(Result);

View File

@@ -10,6 +10,8 @@
#include "Kismet2/Kismet2NameValidators.h" #include "Kismet2/Kismet2NameValidators.h"
#include "UObject/UObjectIterator.h" #include "UObject/UObjectIterator.h"
#include "UObject/UnrealType.h" #include "UObject/UnrealType.h"
#include "WidgetBlueprint.h"
#include "Blueprint/WidgetTree.h"
// Reparent validation // Reparent validation
#include "Engine/LevelScriptActor.h" #include "Engine/LevelScriptActor.h"
@@ -62,6 +64,11 @@ FName WingUtils::CheckInternalizeID(const FString &ExternalID, WingOut Errors)
FName WingUtils::CheckProposedName(const FString &ExternalID, WingOut Errors) FName WingUtils::CheckProposedName(const FString &ExternalID, WingOut Errors)
{ {
FName InternalID = CheckInternalizeID(ExternalID, Errors); FName InternalID = CheckInternalizeID(ExternalID, Errors);
if (InternalID.ToString().Len() > 100)
{
Errors.Printf(TEXT("ERROR: id %s is too long"), *ExternalID);
return FName();
}
if (!InternalID.IsNone() && !WingTokenizer::WouldExternalizeReadably(InternalID)) if (!InternalID.IsNone() && !WingTokenizer::WouldExternalizeReadably(InternalID))
{ {
Errors.Printf(TEXT("ERROR: id %s would not be a readable id, may not create item with this name"), Errors.Printf(TEXT("ERROR: id %s would not be a readable id, may not create item with this name"),
@@ -476,6 +483,21 @@ UObject *WingUtils::GetGeneratedCDO(UBlueprint *BP)
return BP->GeneratedClass->GetDefaultObject(); return BP->GeneratedClass->GetDefaultObject();
} }
void WingUtils::GetAllInUseNames(UBlueprint *BP, TSet<FName> &Names)
{
FBlueprintEditorUtils::GetClassVariableList(BP, Names, true);
FBlueprintEditorUtils::GetFunctionNameList(BP, Names);
FBlueprintEditorUtils::GetAllGraphNames(BP, Names);
FBlueprintEditorUtils::GetSCSVariableNameList(BP, Names);
FBlueprintEditorUtils::GetImplementingBlueprintsFunctionNameList(BP, Names);
if (UWidgetBlueprint *WBP = Cast<UWidgetBlueprint>(BP))
{
TArray<UWidget*> AllWidgets;
WBP->WidgetTree->GetAllWidgets(AllWidgets);
WingUtils::AddNamesToSet(Names, AllWidgets);
}
}
// ============================================================ // ============================================================
// Graph Pin Helpers // Graph Pin Helpers
// ============================================================ // ============================================================

View File

@@ -612,7 +612,7 @@ bool WingVariables::CreateBlueprint(WingOut Errors)
// Check for name collisions against existing variables, components, and the like. // Check for name collisions against existing variables, components, and the like.
TSet<FName> Names; TSet<FName> Names;
FBlueprintEditorUtils::GetClassVariableList(Blueprint.Get(), Names); WingUtils::GetAllInUseNames(Blueprint.Get(), Names);
if (!WingUtils::FindNoDuplicateNames(Names, BlueprintVariables.Variables, TEXT("variable or component"), Errors)) return false; if (!WingUtils::FindNoDuplicateNames(Names, BlueprintVariables.Variables, TEXT("variable or component"), Errors)) return false;
// Create the variables. // Create the variables.

View File

@@ -5,6 +5,7 @@
class UBlueprintNodeSpawner; class UBlueprintNodeSpawner;
class UEdGraph; class UEdGraph;
class UEdGraphNode; class UEdGraphNode;
struct FBlueprintActionContext;
struct FEdGraphSchemaAction; struct FEdGraphSchemaAction;
@@ -20,6 +21,10 @@ struct FWingGraphAction
FWingGraphAction(UBlueprintNodeSpawner *iSpawner, UEdGraph *iGraph); FWingGraphAction(UBlueprintNodeSpawner *iSpawner, UEdGraph *iGraph);
UEdGraphNode *Execute(const FVector2D &Location) const; UEdGraphNode *Execute(const FVector2D &Location) const;
private:
static FName GetSpawnerOwnerClass(const UBlueprintNodeSpawner* Spawner);
static FName GetPropertyClassName(const FProperty *Prop);
}; };

View File

@@ -136,6 +136,12 @@ public:
return Result; return Result;
} }
template<typename ArrayType>
static void AddNamesToSet(TSet<FName> &Collection, ArrayType &&Array)
{
for (auto &Elt : Array) Collection.Add(GetFName(Elt));
}
static bool FindNoDuplicateName(TSet<FName> &Collection, FName InternalID, const TCHAR *Kind, WingOut Errors) static bool FindNoDuplicateName(TSet<FName> &Collection, FName InternalID, const TCHAR *Kind, WingOut Errors)
{ {
if (Collection.Contains(InternalID)) if (Collection.Contains(InternalID))
@@ -244,6 +250,7 @@ public:
static TArray<UEdGraphNode*> AllNodes(UEdGraph *Graph); static TArray<UEdGraphNode*> AllNodes(UEdGraph *Graph);
static TArray<UBlueprint*> GetAncestorBlueprints(UBlueprint *BP, bool OldestFirst = false); static TArray<UBlueprint*> GetAncestorBlueprints(UBlueprint *BP, bool OldestFirst = false);
static UObject *GetGeneratedCDO(UBlueprint *BP); static UObject *GetGeneratedCDO(UBlueprint *BP);
static void GetAllInUseNames(UBlueprint *BP, TSet<FName> &Names);
// ----- Graph pin helpers ----- // ----- Graph pin helpers -----
static UEdGraphPin* CheckGetPin(UEdGraphNode* Node, FName PinName, WingOut Errors); static UEdGraphPin* CheckGetPin(UEdGraphNode* Node, FName PinName, WingOut Errors);