Stricter name validation, and better disambiguation of graph actions
This commit is contained in:
46
AGENTS.md
46
AGENTS.md
@@ -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.)
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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"),
|
||||||
|
|||||||
@@ -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,20 +140,30 @@ 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)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user