Lots more progress on the name overhaul

This commit is contained in:
2026-03-29 02:37:47 -04:00
parent 843e16b177
commit 091bfe1ad2
12 changed files with 106 additions and 233 deletions

View File

@@ -4,6 +4,7 @@
#include "WingHandler.h"
#include "WingFetcher.h"
#include "WingUtils.h"
#include "WingTypes.h"
#include "WingServer.h"
#include "Engine/Blueprint.h"
#include "Kismet2/BlueprintEditorUtils.h"
@@ -41,11 +42,26 @@ public:
UBlueprint* BP = F.Asset(Blueprint).Cast<UBlueprint>();
if (!BP) return;
// Find the interface by name
FBPInterfaceDescription *IFaceDesc =
WingUtils::FindExactlyOneNamed(Interface, BP->ImplementedInterfaces, TEXT("Interface"));
if (!IFaceDesc) return;
UClass* FoundInterface = IFaceDesc->Interface;
// Resolve the interface name to a UClass*
UWingTypes::Requirements Req;
Req.BlueprintType = false;
Req.Blueprintable = false;
Req.AllowContainer = false;
UClass* FoundInterface = UWingTypes::TextToOneInterfaceType(Interface, Req);
if (!FoundInterface) return;
// Verify this blueprint actually implements it
bool Found = false;
for (const FBPInterfaceDescription& Desc : BP->ImplementedInterfaces)
{
if (Desc.Interface == FoundInterface) { Found = true; break; }
}
if (!Found)
{
UWingServer::Printf(TEXT("ERROR: Blueprint %s does not implement interface %s\n"),
*WingUtils::FormatName(BP), *WingUtils::FormatName(FoundInterface));
return;
}
FTopLevelAssetPath InterfacePath = FoundInterface->GetClassPathName();
FBlueprintEditorUtils::RemoveInterface(BP, InterfacePath, PreserveFunctions);

View File

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

View File

@@ -92,12 +92,12 @@ public:
if (!Node) return;
TArray<FWingProperty> All = FWingProperty::GetDetailsMutable(Node, CPF_Edit);
FWingProperty P = FWingProperty::FindOneExactMatch(All, Entry.Name);
FWingProperty *P = WingUtils::FindOneWithExternalID(Entry.Name, All, TEXT("Property"));
if (!P) return;
UWingServer::AddTouchedObject(Node);
if (!P.SetText(Entry.Value))
if (!P->SetText(Entry.Value))
return;
}

View File

@@ -37,10 +37,10 @@ public:
if (!Obj) return;
TArray<FWingProperty> All = FWingProperty::GetDetailsMutable(Obj, CPF_Edit);
FWingProperty P = FWingProperty::FindOneExactMatch(All, Property);
FWingProperty *P = WingUtils::FindOneWithExternalID(Property, All, TEXT("Property"));
if (!P) return;
UWingServer::Print(P.GetText());
UWingServer::Print(P->GetText());
UWingServer::Print(TEXT("\n"));
}
};

View File

@@ -48,15 +48,15 @@ public:
// Validation pass — resolve all properties and values before modifying anything.
for (const auto& Pair : Properties.Json->Values)
{
FWingProperty P = FWingProperty::FindOneExactMatch(All, Pair.Key);
FWingProperty *P = WingUtils::FindOneWithExternalID(Pair.Key, All, TEXT("Property"));
if (!P) return;
}
// Assignment Pass - store the values.
for (const auto& Pair : Properties.Json->Values)
{
FWingProperty P = FWingProperty::FindOneExactMatch(All, Pair.Key);
if (P.SetJson(Pair.Value)) SuccessCount++;
FWingProperty *P = WingUtils::FindOneWithExternalID(Pair.Key, All, TEXT("Property"));
if (P && P->SetJson(Pair.Value)) SuccessCount++;
}
UWingServer::Printf(TEXT("Set %d/%d properties.\n"), SuccessCount, Properties.Json->Values.Num());

View File

@@ -336,27 +336,6 @@ TArray<FWingProperty> FWingProperty::FindAllSubstring(const TArray<FWingProperty
return Result;
}
FWingProperty FWingProperty::FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name)
{
TArray<FWingProperty> Matches;
for (const FWingProperty& P : Props)
{
if (WingUtils::Identifies(Name, P.Prop))
Matches.Add(P);
}
if (Matches.Num() == 0)
{
UWingServer::Printf(TEXT("ERROR: Property '%s' not found\n"), *Name);
return FWingProperty();
}
if (Matches.Num() > 1)
{
UWingServer::Printf(TEXT("ERROR: Ambiguous property '%s'\n"), *Name);
return FWingProperty();
}
return Matches[0];
}
bool FWingProperty::PopulateFromJson(FWingProperty& P, const FJsonObject* Json, bool AllOptional)
{
FString JsonKey = WingUtils::FormatName(P.Prop);

View File

@@ -311,3 +311,14 @@ FString WingTokenizer::CheckInternalizeID(const FString &ExternalID)
}
return InternalID;
}
FString WingTokenizer::SimplifyID(const FString &ID)
{
TStringBuilder<512> Result;
for (TCHAR Ch : ID)
{
if (WingCharacterClasses::GetCat(Ch) == Cat::Identifier)
Result.AppendChar(Ch);
}
return Result.ToString();
}

View File

@@ -2,6 +2,7 @@
#include "WingUtils.h"
#include "WingServer.h"
#include "WingManual.h"
#include "WingTokenizer.h"
#include "Editor.h"
#include "EdGraphSchema_K2.h"
#include "Engine/Blueprint.h"
@@ -161,15 +162,17 @@ FString UWingTypes::NewShortName(const FString &Path, FName PinCategory, const U
// Verify that the path is not already associated.
check(!PathToShort.Find(Path));
// Derive the name proposal from the path.
// Derive the name proposal from the path. Names used by
// the type system are always simple ids that require no
// escaping when emitted.
int32 DotIndex;
check(Path.FindLastChar('.', DotIndex));
FString Proposal = Path.Mid(DotIndex + 1);
if ((PinCategory == UEdGraphSchema_K2::PC_Object || PinCategory == UEdGraphSchema_K2::PC_Interface)
&& IsUserDefined && Proposal.EndsWith(TEXT("_C")))
Proposal.LeftChopInline(2);
Proposal = WingUtils::ExternalizeID(Proposal);
check(!Proposal.IsEmpty());
Proposal = WingTokenizer::SimplifyID(Proposal);
if (Proposal.IsEmpty()) Proposal = "Unknown";
// Construct the Info struct.
Info TypeInfo;

View File

@@ -55,10 +55,7 @@
// Name sanitization
// ============================================================
FString WingUtils::ExternalizeID(const FString &InName)
{
return WingTokenizer::ExternalizeID(InName);
}
FName WingUtils::GetFName(const FWingProperty &Prop) { return Prop.Prop->GetFName(); }
FString WingUtils::ExternalizeID(FName Name)
{
@@ -126,7 +123,7 @@ FString WingUtils::FormatName(const UBlueprint *BP)
FString WingUtils::FormatName(const UActorComponent *C)
{
return ExternalizeID(C->GetName());
return ExternalizeID(C->GetFName());
}
FString WingUtils::FormatName(const USCS_Node *Node)
@@ -136,22 +133,22 @@ FString WingUtils::FormatName(const USCS_Node *Node)
FString WingUtils::FormatName(const UEdGraph *Graph)
{
return ExternalizeID(Graph->GetName());
return ExternalizeID(Graph->GetFName());
}
FString WingUtils::FormatName(const TObjectPtr<UEdGraph> &Graph)
{
return ExternalizeID(Graph->GetName());
return ExternalizeID(Graph->GetFName());
}
FString WingUtils::FormatName(const UEdGraphNode* Node)
{
return ExternalizeID(Node->GetName());
return ExternalizeID(Node->GetFName());
}
FString WingUtils::FormatName(const UEdGraphPin *Pin)
{
return ExternalizeID(Pin->GetName());
return ExternalizeID(Pin->GetFName());
}
FString WingUtils::FormatName(const FMemberReference &Ref)
@@ -168,7 +165,7 @@ FString WingUtils::FormatName(const UStruct *Struct)
{
if (Cast<UScriptStruct>(Struct) || Cast<UClass>(Struct))
return UWingTypes::TypeToTextOrDie(Struct);
return ExternalizeID(Struct->GetName());
return ExternalizeID(Struct->GetFName());
}
FString WingUtils::FormatName(const UClass *Class)
@@ -193,7 +190,7 @@ FString WingUtils::FormatName(const UMaterialFunction *MaterialFunction)
FString WingUtils::FormatName(const UMaterialExpression *Expression)
{
return ExternalizeID(Expression->GetName());
return ExternalizeID(Expression->GetFName());
}
FString WingUtils::FormatName(const UStaticMesh *Mesh)
@@ -233,7 +230,7 @@ FString WingUtils::FormatName(const UEnum *Enum)
FString WingUtils::FormatName(const FProperty *Prop)
{
return ExternalizeID(Prop->GetName());
return ExternalizeID(Prop->GetFName());
}
FString WingUtils::FormatName(const FUserPinInfo &Pin)
@@ -253,7 +250,7 @@ FString WingUtils::FormatName(const UWingComponentReference *Ref)
FString WingUtils::FormatName(const UWidget *Widget)
{
return ExternalizeID(Widget->GetName());
return ExternalizeID(Widget->GetFName());
}
// ============================================================

View File

@@ -62,7 +62,6 @@ struct FWingProperty
// Functions to find items by name in an array of properties.
//
static TArray<FWingProperty> FindAllSubstring(const TArray<FWingProperty>& Props, const FString& Substring);
static FWingProperty FindOneExactMatch(const TArray<FWingProperty>& Props, const FString& Name);
static void Remove(TArray<FWingProperty>& Props, const FString& Name);
static void Move(TArray<FWingProperty> &Out, TArray<FWingProperty> &In, const FString &Name);

View File

@@ -150,6 +150,13 @@ struct WingTokenizer
// string.
static FString CheckInternalizeID(const FString &ExternalID);
// Simplify an ID. This removes any non-identifier
// characters from the ID. Be careful! This could
// remove the whole identifier! So obviously this
// should only be used in certain rare contexts where
// that's OK.
static FString SimplifyID(const FString &ID);
// Print all tokens to the log for debugging.
void PrintEverything() const;

View File

@@ -22,6 +22,7 @@ class UAnimStateTransitionNode;
class UScriptStruct;
class UEnum;
struct FBPInterfaceDescription;
struct FWingProperty;
#include "Engine/World.h"
#include "Materials/Material.h"
@@ -37,21 +38,14 @@ struct FBPInterfaceDescription;
class WingUtils
{
public:
// Given an array and a reference to an element of the array, return
// a pointer to the element, or if the element is already a pointer, the element.
template<class T> static const T* EltAsPtr(const TArray<T> &A, const T &Elt) { return &Elt; }
template<class T> static T* EltAsPtr(TArray<T> &A, T &Elt) { return &Elt; }
template<class T> static T* EltAsPtr(const TArray<T*> &A, T* &Elt) { return Elt; }
template<class T> static T* EltAsPtr(TArray<T*> &A, T* &Elt) { return Elt; }
template<class T> static const T* EltAsPtr(const TArray<const T*> &A, const T* &Elt) { return Elt; }
template<class T> static const T* EltAsPtr(TArray<const T*> &A, const T* &Elt) { return Elt; }
////////////////////////////////////////////////////////
//
// GetFName
//
// For objects whose FormatName uses ExternalizeID,
// GetFName returns the raw internal FName.
// Get the FName of anything that has an FName. This
// is here because having a consistent way to get the FName
// of something makes it possible to write search templates
// that work on any of these types.
//
////////////////////////////////////////////////////////
@@ -64,10 +58,45 @@ public:
static FName GetFName(const FBPVariableDescription &Var) { return Var.VarName; }
static FName GetFName(const UMaterialExpression *Expression) { return Expression->GetFName(); }
static FName GetFName(const FProperty *Prop) { return Prop->GetFName(); }
static FName GetFName(const FWingProperty &Prop);
static FName GetFName(const FUserPinInfo &Pin) { return Pin.PinName; }
static FName GetFName(const UWingComponentReference *Ref) { return Ref->VariableName; }
static FName GetFName(const UWidget *Widget) { return Widget->GetFName(); }
////////////////////////////////////////////////////////
//
// When searching an array of structs, you want to
// return a pointer to one of the structs. When
// searching an array of pointers, you want to return
// one of the pointers. These helpers make it easy for
// the search templates below to do that.
//
////////////////////////////////////////////////////////
template<class T> static const T* EltAsPtr(const TArray<T> &A, const T &Elt) { return &Elt; }
template<class T> static T* EltAsPtr(TArray<T> &A, T &Elt) { return &Elt; }
template<class T> static T* EltAsPtr(const TArray<T*> &A, T* &Elt) { return Elt; }
template<class T> static T* EltAsPtr(TArray<T*> &A, T* &Elt) { return Elt; }
template<class T> static const T* EltAsPtr(const TArray<const T*> &A, const T* &Elt) { return Elt; }
template<class T> static const T* EltAsPtr(TArray<const T*> &A, const T* &Elt) { return Elt; }
////////////////////////////////////////////////////////
//
// Search Templates. Search an array for an item with a
// given name. Works correctly no matter how a name is
// expressed: eg, "A+B" finds the same results as
// "A&plus;B". Handles failure cases correctly: two
// items with the same name, no such item, and malformed
// names. In case of failure, sends readable and
// informative error messages to the AI Agent.
//
// Try to direct all searching through these templates,
// given the amount of care that was put into ensuring
// their correctness and the quality of their error
// reporting.
//
////////////////////////////////////////////////////////
template<typename ArrayType>
static auto FindOneWithInternalID(FName InternalID, ArrayType &&Array, const TCHAR *Kind)
{
@@ -103,32 +132,13 @@ public:
return FindNoneWithInternalID(InternalID, Array, Kind);
}
////////////////////////////////////////////////////////
//
// GetPathName
//
// For objects whose FormatName returns a path name,
// GetPathName returns the raw path.
//
////////////////////////////////////////////////////////
static FString GetPathName(const UWorld *World) { return World->GetPathName(); }
static FString GetPathName(const UMaterial *Material) { return Material->GetPathName(); }
static FString GetPathName(const UMaterialInstance *MI) { return MI->GetPathName(); }
static FString GetPathName(const UMaterialFunction *MF) { return MF->GetPathName(); }
static FString GetPathName(const UStaticMesh *Mesh) { return Mesh->GetPathName(); }
static FString GetPathName(const USkeletalMesh *Mesh) { return Mesh->GetPathName(); }
static FString GetPathName(const UAnimSequence *Anim) { return Anim->GetPathName(); }
static FString GetPathName(const UTexture *Texture) { return Texture->GetPathName(); }
////////////////////////////////////////////////////////
//
// Name Formatting
//
// The goal here is to centralize the code that outputs
// names, and have everybody use it, so that names are
// used consistently. When an identifier is output by
// FormatName, it's always an ExternalID.
// Get the name of something, and format it for transmission
// to the AI Agent. These correctly handle all escaping and
// quoting.
//
////////////////////////////////////////////////////////
@@ -161,54 +171,9 @@ public:
static FString FormatName(const UWingComponentReference *Ref);
static FString FormatName(const UWidget *Widget);
////////////////////////////////////////////////////////
// Identifies
//
// Return true if the name matches the formatted name
// of the object, using the formatname routines above.
////////////////////////////////////////////////////////
template<typename T>
static bool Identifies(const FString &ExternalID, T&& Obj)
{
return FormatName(std::forward<T>(Obj)).Equals(ExternalID, ESearchCase::IgnoreCase);
}
////////////////////////////////////////////////////////
template<typename T>
static T* FindExactlyOneNamed(const FString &Name, const TArray<T*> &Array, const TCHAR *Kind)
{
int Count = 0;
T* Result = nullptr;
for (T* Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = Elt; }
if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr;
return Result;
}
template<typename T, typename = std::enable_if_t<!std::is_pointer_v<T>>>
static T* FindExactlyOneNamed(const FString &Name, TArray<T> &Array, const TCHAR *Kind)
{
int Count = 0;
T* Result = nullptr;
for (T& Elt : Array) if (Identifies(Name, Elt)) { Count++; Result = &Elt; }
if (!CheckExactlyOneNamed(Count, Kind, Name)) return nullptr;
return Result;
}
template<typename T>
static bool FindExactlyNoneNamed(const FString &Name, const TArray<T> &Array, const TCHAR *Kind)
{
for (const T& Elt: Array) if (Identifies(Name, Elt))
{
return CheckExactlyNoneNamed(1, Kind, Name);
}
return true;
}
////////////////////////////////////////////////////////
static FString ExternalizeID(const FString& InternalID);
// static FString ExternalizeID(const FString& InternalID);
static FString ExternalizeID(FName Name);
static FName CheckInternalizeID(const FString &ExternalID);