MCPFetcher, and new code for FormatName
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,4 +1,5 @@
|
||||
#include "BlueprintExporter.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
@@ -26,37 +27,6 @@ FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph)
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
FString FlxBlueprintExporter::SanitizeName(const FString& Title)
|
||||
{
|
||||
FString Result = Title.TrimStartAndEnd().Replace(TEXT(" "), TEXT("_"));
|
||||
return Result.IsEmpty() ? TEXT("_") : Result;
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatPinType(const FEdGraphPinType& PinType)
|
||||
{
|
||||
if (UObject* SubObj = PinType.PinSubCategoryObject.Get())
|
||||
{
|
||||
return SubObj->GetName();
|
||||
}
|
||||
FString Type = PinType.PinCategory.ToString();
|
||||
Type[0] = FChar::ToUpper(Type[0]);
|
||||
return Type;
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatPinType(UEdGraphPin* Pin)
|
||||
{
|
||||
return FormatPinType(Pin->PinType);
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatNodeBaseName(UEdGraphNode* Node)
|
||||
{
|
||||
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
||||
int32 NewlineIdx;
|
||||
if (Title.FindChar(TEXT('\n'), NewlineIdx))
|
||||
Title.LeftInline(NewlineIdx);
|
||||
return SanitizeName(Title);
|
||||
}
|
||||
|
||||
UEdGraphPin* FlxBlueprintExporter::GetLinkedTo(UEdGraphPin* Pin)
|
||||
{
|
||||
while (true)
|
||||
@@ -114,60 +84,6 @@ UEdGraphPin* FlxBlueprintExporter::FindFirstPin(UEdGraphNode* Node, EEdGraphPinD
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UEdGraphPin* FlxBlueprintExporter::BestMatchPin(UEdGraphNode* Node, EEdGraphPinDirection Direction, bool Exec, const FString& Name)
|
||||
{
|
||||
if (Name == TEXT("_")) return nullptr;
|
||||
|
||||
UEdGraphPin* DisplayMatch = nullptr;
|
||||
int32 DisplayCount = 0;
|
||||
UEdGraphPin* RawMatch = nullptr;
|
||||
int32 RawCount = 0;
|
||||
|
||||
for (UEdGraphPin* Pin : Node->Pins)
|
||||
{
|
||||
if (Pin->Direction != Direction) continue;
|
||||
bool PinIsExec = (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec);
|
||||
if (PinIsExec != Exec) continue;
|
||||
|
||||
if (Name == SanitizeName(Node->GetPinDisplayName(Pin).ToString()))
|
||||
{
|
||||
DisplayMatch = Pin;
|
||||
DisplayCount++;
|
||||
}
|
||||
|
||||
if (Name == SanitizeName(Pin->PinName.ToString()))
|
||||
{
|
||||
RawMatch = Pin;
|
||||
RawCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (DisplayCount == 1) return DisplayMatch;
|
||||
if (RawCount == 1) return RawMatch;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatPinName(UEdGraphPin *Pin)
|
||||
{
|
||||
UEdGraphNode* Node = Pin->GetOwningNode();
|
||||
bool Exec = (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec);
|
||||
|
||||
// Try sanitized display name first.
|
||||
FString SanitizedDisplay = SanitizeName(Node->GetPinDisplayName(Pin).ToString());
|
||||
if (BestMatchPin(Node, Pin->Direction, Exec, SanitizedDisplay) == Pin)
|
||||
return SanitizedDisplay;
|
||||
|
||||
// Try sanitized raw name.
|
||||
FString SanitizedRaw = SanitizeName(Pin->PinName.ToString());
|
||||
if (BestMatchPin(Node, Pin->Direction, Exec, SanitizedRaw) == Pin)
|
||||
return SanitizedRaw;
|
||||
|
||||
// No unambiguous name found.
|
||||
UE_LOG(LogTemp, Warning, TEXT("Blueprint export: ambiguous pin name '%s' on node '%s'"),
|
||||
*Pin->PinName.ToString(), *Node->GetNodeTitle(ENodeTitleType::ListView).ToString());
|
||||
return FString::Printf(TEXT("?%s"), *SanitizedRaw);
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -175,12 +91,6 @@ FString FlxBlueprintExporter::FormatPinName(UEdGraphPin *Pin)
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
FString FlxBlueprintExporter::FormatNodeName(UEdGraphNode* Node)
|
||||
{
|
||||
FString* Name = NodeNames.Find(Node);
|
||||
return Name ? *Name : FormatNodeBaseName(Node);
|
||||
}
|
||||
|
||||
FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
||||
{
|
||||
// If connected, show source node.pin
|
||||
@@ -191,10 +101,10 @@ FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
||||
|
||||
// For variable get nodes, just show the variable name.
|
||||
if (UK2Node_VariableGet* VarGet = Cast<UK2Node_VariableGet>(LinkedToNode))
|
||||
return SanitizeName(VarGet->GetVarNameString());
|
||||
return MCPUtils::FormatName(VarGet->VariableReference);
|
||||
|
||||
FString PinLabel = FormatPinName(LinkedTo);
|
||||
return FString::Printf(TEXT("%s.%s"), *FormatNodeName(LinkedToNode), *PinLabel);
|
||||
FString PinLabel = MCPUtils::FormatName(LinkedTo);
|
||||
return FString::Printf(TEXT("%s.%s"), *MCPUtils::FormatName(LinkedToNode), *PinLabel);
|
||||
}
|
||||
|
||||
// String pins: always show in quotes (even if empty).
|
||||
@@ -286,16 +196,7 @@ void FlxBlueprintExporter::SortNodes()
|
||||
|
||||
void FlxBlueprintExporter::AssignNodeNames()
|
||||
{
|
||||
TMap<FString, int32> NextIndex;
|
||||
|
||||
for (UEdGraphNode* Node : SortedNodes)
|
||||
{
|
||||
FString Base = FormatNodeBaseName(Node);
|
||||
int32& Idx = NextIndex.FindOrAdd(Base, 0);
|
||||
FString Name = (Idx == 0) ? Base : FString::Printf(TEXT("%s_%d"), *Base, Idx + 1);
|
||||
NodeNames.Add(Node, Name);
|
||||
Idx++;
|
||||
}
|
||||
// Node names are now computed on the fly by FormatNodeName.
|
||||
}
|
||||
|
||||
void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
||||
@@ -306,7 +207,7 @@ void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
||||
return;
|
||||
}
|
||||
|
||||
Output.Appendf(TEXT("\nnode %s\n"), *FormatNodeName(Node));
|
||||
Output.Appendf(TEXT("\nnode %s\n"), *MCPUtils::FormatName(Node));
|
||||
|
||||
// Emit input data pins.
|
||||
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
|
||||
@@ -315,8 +216,8 @@ void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
||||
if (Pin->bHidden) continue;
|
||||
|
||||
Output.Appendf(TEXT(" input %s %s = %s\n"),
|
||||
*FormatPinType(Pin),
|
||||
*FormatPinName(Pin),
|
||||
*MCPUtils::FormatPinType(Pin),
|
||||
*MCPUtils::FormatName(Pin),
|
||||
*FormatPinSource(Pin));
|
||||
}
|
||||
|
||||
@@ -327,7 +228,7 @@ void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
||||
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;
|
||||
if (Pin->bHidden) continue;
|
||||
if (!ReturnPins.IsEmpty()) ReturnPins += TEXT(", ");
|
||||
ReturnPins += FormatPinName(Pin);
|
||||
ReturnPins += MCPUtils::FormatName(Pin);
|
||||
}
|
||||
if (!ReturnPins.IsEmpty())
|
||||
{
|
||||
@@ -341,12 +242,12 @@ void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
||||
UEdGraphPin* LinkedTo = GetLinkedTo(Pin);
|
||||
if (!LinkedTo) continue;
|
||||
|
||||
FString Target = FormatNodeName(LinkedTo->GetOwningNode());
|
||||
FString Target = MCPUtils::FormatName(LinkedTo->GetOwningNode());
|
||||
|
||||
if (ExecOuts.Num() == 1)
|
||||
Output.Appendf(TEXT(" goto %s\n"), *Target);
|
||||
else
|
||||
Output.Appendf(TEXT(" goto-if %s %s\n"), *FormatPinName(Pin), *Target);
|
||||
Output.Appendf(TEXT(" goto-if %s %s\n"), *MCPUtils::FormatName(Pin), *Target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,8 +262,8 @@ void FlxBlueprintExporter::EmitLocalVariables()
|
||||
{
|
||||
FString Default = Var.DefaultValue.IsEmpty() ? TEXT("<default>") : Var.DefaultValue;
|
||||
Output.Appendf(TEXT("local %s %s = %s\n"),
|
||||
*FormatPinType(Var.VarType),
|
||||
*SanitizeName(Var.VarName.ToString()),
|
||||
*MCPUtils::FormatPinType(Var.VarType),
|
||||
*MCPUtils::FormatName(Var),
|
||||
*Default);
|
||||
}
|
||||
break;
|
||||
@@ -387,6 +288,6 @@ void FlxBlueprintExporter::EmitNodeList()
|
||||
if (Node->IsA<UEdGraphNode_Comment>()) continue;
|
||||
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
||||
Details.Appendf(TEXT("%s = %s\n"),
|
||||
*FormatNodeName(Node), *Node->NodeGuid.ToString());
|
||||
*MCPUtils::FormatName(Node), *Node->NodeGuid.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,14 +2,10 @@
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "MCPHandler.h"
|
||||
#include "MCPAssetFinder.h"
|
||||
#include "MCPFetcher.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "EdGraphSchema_K2.h"
|
||||
#include "UObject/UObjectIterator.h"
|
||||
#include "UMCPHandler_GetPinDetails.generated.h"
|
||||
|
||||
|
||||
@@ -27,14 +23,8 @@ class UMCPHandler_GetPinDetails : public UObject, public IMCPHandler
|
||||
GENERATED_BODY()
|
||||
|
||||
public:
|
||||
UPROPERTY(meta=(Description="Blueprint name or package path"))
|
||||
FString Blueprint;
|
||||
|
||||
UPROPERTY(meta=(Description="Node to look up (GUID)"))
|
||||
FString Node;
|
||||
|
||||
UPROPERTY(meta=(Description="Pin name on the node"))
|
||||
FString PinName;
|
||||
UPROPERTY(meta=(Description="Path to the pin, e.g. /Game/Widgets/WB_Hotkeys,node:MyNode,pin:Result"))
|
||||
FString Pin;
|
||||
|
||||
virtual FString GetDescription() const override
|
||||
{
|
||||
@@ -44,75 +34,47 @@ public:
|
||||
|
||||
virtual void Handle(const FJsonObject* Json, FJsonObject* Result) override
|
||||
{
|
||||
MCPAssets<UBlueprint> Assets;
|
||||
if (!Assets.Exact(Blueprint).Errors(Result).ENone().ETwo().Load()) return;
|
||||
UBlueprint* BP = Assets.Object();
|
||||
MCPFetcher F(Result);
|
||||
UEdGraphPin* P = F.Walk(Pin).Cast<UEdGraphPin>();
|
||||
if (!P) return;
|
||||
|
||||
UEdGraph* Graph = nullptr;
|
||||
UEdGraphNode* FoundNode = MCPUtils::FindNodeByGuid(BP, Node, &Graph);
|
||||
if (!FoundNode)
|
||||
Result->SetStringField(TEXT("pinName"), P->PinName.ToString());
|
||||
Result->SetStringField(TEXT("direction"), P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output"));
|
||||
Result->SetStringField(TEXT("type"), P->PinType.PinCategory.ToString());
|
||||
|
||||
if (!P->PinType.PinSubCategory.IsNone())
|
||||
{
|
||||
return MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Node '%s' not found"), *Node));
|
||||
Result->SetStringField(TEXT("subCategory"), P->PinType.PinSubCategory.ToString());
|
||||
}
|
||||
if (P->PinType.PinSubCategoryObject.IsValid())
|
||||
{
|
||||
Result->SetStringField(TEXT("subtype"), P->PinType.PinSubCategoryObject->GetName());
|
||||
}
|
||||
|
||||
UEdGraphPin* Pin = FoundNode->FindPin(FName(*PinName));
|
||||
if (!Pin)
|
||||
{
|
||||
// List available pins
|
||||
TArray<TSharedPtr<FJsonValue>> AvailPins;
|
||||
for (UEdGraphPin* P : FoundNode->Pins)
|
||||
{
|
||||
if (P)
|
||||
{
|
||||
TSharedRef<FJsonObject> PinObj = MakeShared<FJsonObject>();
|
||||
PinObj->SetStringField(TEXT("name"), P->PinName.ToString());
|
||||
PinObj->SetStringField(TEXT("direction"), P->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output"));
|
||||
PinObj->SetStringField(TEXT("type"), P->PinType.PinCategory.ToString());
|
||||
AvailPins.Add(MakeShared<FJsonValueObject>(PinObj));
|
||||
}
|
||||
}
|
||||
MCPUtils::MakeErrorJson(Result, FString::Printf(TEXT("Pin '%s' not found on node '%s'"), *PinName, *Node));
|
||||
Result->SetArrayField(TEXT("availablePins"), AvailPins);
|
||||
return;
|
||||
}
|
||||
Result->SetBoolField(TEXT("isArray"), P->PinType.IsArray());
|
||||
Result->SetBoolField(TEXT("isSet"), P->PinType.IsSet());
|
||||
Result->SetBoolField(TEXT("isMap"), P->PinType.IsMap());
|
||||
Result->SetBoolField(TEXT("isReference"), P->PinType.bIsReference);
|
||||
Result->SetBoolField(TEXT("isConst"), P->PinType.bIsConst);
|
||||
|
||||
Result->SetStringField(TEXT("pinName"), Pin->PinName.ToString());
|
||||
Result->SetStringField(TEXT("direction"), Pin->Direction == EGPD_Input ? TEXT("Input") : TEXT("Output"));
|
||||
Result->SetStringField(TEXT("type"), Pin->PinType.PinCategory.ToString());
|
||||
|
||||
if (!Pin->PinType.PinSubCategory.IsNone())
|
||||
if (!P->DefaultValue.IsEmpty())
|
||||
{
|
||||
Result->SetStringField(TEXT("subCategory"), Pin->PinType.PinSubCategory.ToString());
|
||||
Result->SetStringField(TEXT("defaultValue"), P->DefaultValue);
|
||||
}
|
||||
if (Pin->PinType.PinSubCategoryObject.IsValid())
|
||||
if (!P->DefaultTextValue.IsEmpty())
|
||||
{
|
||||
Result->SetStringField(TEXT("subtype"), Pin->PinType.PinSubCategoryObject->GetName());
|
||||
Result->SetStringField(TEXT("defaultTextValue"), P->DefaultTextValue.ToString());
|
||||
}
|
||||
|
||||
Result->SetBoolField(TEXT("isArray"), Pin->PinType.IsArray());
|
||||
Result->SetBoolField(TEXT("isSet"), Pin->PinType.IsSet());
|
||||
Result->SetBoolField(TEXT("isMap"), Pin->PinType.IsMap());
|
||||
Result->SetBoolField(TEXT("isReference"), Pin->PinType.bIsReference);
|
||||
Result->SetBoolField(TEXT("isConst"), Pin->PinType.bIsConst);
|
||||
|
||||
if (!Pin->DefaultValue.IsEmpty())
|
||||
if (P->DefaultObject)
|
||||
{
|
||||
Result->SetStringField(TEXT("defaultValue"), Pin->DefaultValue);
|
||||
}
|
||||
if (!Pin->DefaultTextValue.IsEmpty())
|
||||
{
|
||||
Result->SetStringField(TEXT("defaultTextValue"), Pin->DefaultTextValue.ToString());
|
||||
}
|
||||
if (Pin->DefaultObject)
|
||||
{
|
||||
Result->SetStringField(TEXT("defaultObject"), Pin->DefaultObject->GetPathName());
|
||||
Result->SetStringField(TEXT("defaultObject"), P->DefaultObject->GetPathName());
|
||||
}
|
||||
|
||||
// Connected pins
|
||||
if (Pin->LinkedTo.Num() > 0)
|
||||
if (P->LinkedTo.Num() > 0)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Conns;
|
||||
for (UEdGraphPin* Linked : Pin->LinkedTo)
|
||||
for (UEdGraphPin* Linked : P->LinkedTo)
|
||||
{
|
||||
if (!Linked || !Linked->GetOwningNode()) continue;
|
||||
TSharedRef<FJsonObject> CJ = MakeShared<FJsonObject>();
|
||||
|
||||
239
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp
Normal file
239
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/MCPFetcher.cpp
Normal file
@@ -0,0 +1,239 @@
|
||||
#include "MCPFetcher.h"
|
||||
#include "MCPUtils.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
#include "UObject/PropertyAccessUtil.h"
|
||||
#include "Engine/SimpleConstructionScript.h"
|
||||
#include "Engine/SCS_Node.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/LevelScriptBlueprint.h"
|
||||
|
||||
MCPFetcher& MCPFetcher::SetError(const FString& Msg)
|
||||
{
|
||||
bError = true;
|
||||
ErrorCB.SetError(Msg);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::TypeMismatch(const TCHAR* Walker, const TCHAR* Expected)
|
||||
{
|
||||
bError = true;
|
||||
if (ResultPin)
|
||||
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is a pin, expected %s"), Walker, Expected));
|
||||
else if (Obj)
|
||||
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is %s, expected %s"), Walker, *Obj->GetClass()->GetName(), Expected));
|
||||
else
|
||||
ErrorCB.SetError(FString::Printf(TEXT("Input to '%s' is null, expected %s"), Walker, Expected));
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Walk(const FString& Path)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
// Split on commas
|
||||
TArray<FString> Segments;
|
||||
Path.ParseIntoArray(Segments, TEXT(","));
|
||||
if (Segments.Num() == 0)
|
||||
{
|
||||
SetError(TEXT("Empty path"));
|
||||
return *this;
|
||||
}
|
||||
|
||||
// If no object yet, first segment is an asset path
|
||||
int32 Start = 0;
|
||||
if (!Obj && !ResultPin)
|
||||
{
|
||||
LoadUAsset(Segments[0]);
|
||||
if (bError) return *this;
|
||||
Start = 1;
|
||||
}
|
||||
|
||||
// Walk each subsequent segment
|
||||
for (int32 i = Start; i < Segments.Num(); i++)
|
||||
{
|
||||
FString Key, Value;
|
||||
Segments[i].Split(TEXT(":"), &Key, &Value);
|
||||
|
||||
if (StrEq(Key, TEXT("graph"))) Graph(Value);
|
||||
else if (StrEq(Key, TEXT("node"))) Node(Value);
|
||||
else if (StrEq(Key, TEXT("pin"))) Pin(Value);
|
||||
else if (StrEq(Key, TEXT("component"))) Component(Value);
|
||||
else if (StrEq(Key, TEXT("property"))) Property(Value);
|
||||
else if (StrEq(Key, TEXT("levelblueprint"))) LevelBlueprint();
|
||||
else
|
||||
{
|
||||
SetError(FString::Printf(TEXT("Unknown walker '%s'"), *Key));
|
||||
return *this;
|
||||
}
|
||||
|
||||
if (bError) return *this;
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void MCPFetcher::LoadUAsset(const FString& PackagePath)
|
||||
{
|
||||
SetObj(LoadObject<UObject>(nullptr, *PackagePath));
|
||||
if (!Obj)
|
||||
SetError(FString::Printf(TEXT("Could not load asset '%s'"), *PackagePath));
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Graph(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
UBlueprint* BP = ::Cast<UBlueprint>(Obj);
|
||||
if (!BP)
|
||||
return TypeMismatch(TEXT("graph"), TEXT("Blueprint"));
|
||||
|
||||
TArray<UEdGraph*> Matches = MCPUtils::AllGraphsNamed(BP, Value);
|
||||
if (Matches.Num() == 0)
|
||||
return SetError(FString::Printf(TEXT("Graph '%s' not found in %s"), *Value, *BP->GetName()));
|
||||
if (Matches.Num() > 1)
|
||||
return SetError(FString::Printf(TEXT("Ambiguous graph '%s' in %s — %d matches"), *Value, *BP->GetName(), Matches.Num()));
|
||||
|
||||
SetObj(Matches[0]);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Node(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
// If current object is a graph, search that graph
|
||||
if (UEdGraph* G = ::Cast<UEdGraph>(Obj))
|
||||
{
|
||||
UEdGraphNode* Found = nullptr;
|
||||
for (UEdGraphNode* N : G->Nodes)
|
||||
{
|
||||
if (!N || !MCPUtils::Identifies(Value, N))
|
||||
continue;
|
||||
if (Found)
|
||||
return SetError(FString::Printf(TEXT("Ambiguous node '%s' in graph %s"), *Value, *G->GetName()));
|
||||
Found = N;
|
||||
}
|
||||
if (!Found)
|
||||
return SetError(FString::Printf(TEXT("Node '%s' not found in graph %s"), *Value, *G->GetName()));
|
||||
SetObj(Found);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// If current object is a blueprint, search all graphs
|
||||
if (UBlueprint* BP = ::Cast<UBlueprint>(Obj))
|
||||
{
|
||||
UEdGraphNode* Found = nullptr;
|
||||
for (UEdGraph* G : MCPUtils::AllGraphs(BP))
|
||||
{
|
||||
for (UEdGraphNode* N : G->Nodes)
|
||||
{
|
||||
if (!N || !MCPUtils::Identifies(Value, N))
|
||||
continue;
|
||||
if (Found)
|
||||
return SetError(FString::Printf(TEXT("Ambiguous node '%s' in %s"), *Value, *BP->GetName()));
|
||||
Found = N;
|
||||
}
|
||||
}
|
||||
if (!Found)
|
||||
return SetError(FString::Printf(TEXT("Node '%s' not found in %s"), *Value, *BP->GetName()));
|
||||
SetObj(Found);
|
||||
return *this;
|
||||
}
|
||||
|
||||
return TypeMismatch(TEXT("node"), TEXT("graph or Blueprint"));
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Pin(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
UEdGraphNode* N = ::Cast<UEdGraphNode>(Obj);
|
||||
if (!N)
|
||||
return TypeMismatch(TEXT("pin"), TEXT("node"));
|
||||
UEdGraphPin* Found = nullptr;
|
||||
for (UEdGraphPin *P : N->Pins)
|
||||
{
|
||||
if (!MCPUtils::Identifies(Value, P))
|
||||
continue;
|
||||
if (Found)
|
||||
return SetError(FString::Printf(TEXT("Ambiguous pin '%s' on node %s"),
|
||||
*Value, *MCPUtils::FormatName(N)));
|
||||
Found = P;
|
||||
}
|
||||
if (!Found)
|
||||
return SetError(FString::Printf(TEXT("Pin '%s' not found on node %s"),
|
||||
*Value, *MCPUtils::FormatName(N)));
|
||||
|
||||
SetPin(Found);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Component(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
UBlueprint* BP = ::Cast<UBlueprint>(Obj);
|
||||
if (!BP)
|
||||
return TypeMismatch(TEXT("component"), TEXT("Blueprint"));
|
||||
|
||||
USimpleConstructionScript* SCS = BP->SimpleConstructionScript;
|
||||
if (!SCS)
|
||||
return SetError(FString::Printf(TEXT("Blueprint %s has no SimpleConstructionScript (not an Actor Blueprint)"), *BP->GetName()));
|
||||
|
||||
FName SearchName(*Value);
|
||||
for (USCS_Node* SCSNode : SCS->GetAllNodes())
|
||||
{
|
||||
if (SCSNode && SCSNode->GetVariableName() == SearchName)
|
||||
{
|
||||
SetObj(SCSNode->ComponentTemplate);
|
||||
return *this;
|
||||
}
|
||||
}
|
||||
|
||||
return SetError(FString::Printf(TEXT("Component '%s' not found in %s"), *Value, *BP->GetName()));
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::Property(const FString& Value)
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
if (!Obj)
|
||||
return TypeMismatch(TEXT("property"), TEXT("UObject"));
|
||||
|
||||
FProperty* Prop = Obj->GetClass()->FindPropertyByName(FName(*Value));
|
||||
if (!Prop)
|
||||
return SetError(FString::Printf(TEXT("Property '%s' not found on %s"), *Value, *Obj->GetClass()->GetName()));
|
||||
|
||||
FObjectProperty* ObjProp = CastField<FObjectProperty>(Prop);
|
||||
if (!ObjProp)
|
||||
return SetError(FString::Printf(TEXT("Property '%s' is not an object property (type: %s)"), *Value, *Prop->GetCPPType()));
|
||||
|
||||
UObject* PropValue = ObjProp->GetObjectPropertyValue(Prop->ContainerPtrToValuePtr<void>(Obj));
|
||||
if (!PropValue)
|
||||
return SetError(FString::Printf(TEXT("Property '%s' is null on %s"), *Value, *Obj->GetName()));
|
||||
|
||||
SetObj(PropValue);
|
||||
return *this;
|
||||
}
|
||||
|
||||
MCPFetcher& MCPFetcher::LevelBlueprint()
|
||||
{
|
||||
if (bError) return *this;
|
||||
|
||||
UWorld* World = ::Cast<UWorld>(Obj);
|
||||
if (!World)
|
||||
return TypeMismatch(TEXT("levelblueprint"), TEXT("World"));
|
||||
|
||||
if (!World->PersistentLevel)
|
||||
return SetError(TEXT("World has no PersistentLevel"));
|
||||
|
||||
ULevelScriptBlueprint* LevelBP = World->PersistentLevel->GetLevelScriptBlueprint(true);
|
||||
if (!LevelBP)
|
||||
return SetError(TEXT("World has no level blueprint"));
|
||||
|
||||
SetObj(LevelBP);
|
||||
return *this;
|
||||
}
|
||||
@@ -7,6 +7,9 @@
|
||||
#include "Serialization/JsonWriter.h"
|
||||
#include "Serialization/JsonSerializer.h"
|
||||
#include "Engine/Blueprint.h"
|
||||
#include "Engine/MemberReference.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Components/ActorComponent.h"
|
||||
#include "EdGraph/EdGraph.h"
|
||||
#include "EdGraph/EdGraphNode.h"
|
||||
#include "EdGraph/EdGraphPin.h"
|
||||
@@ -98,6 +101,157 @@ MCPErrorCallback::MCPErrorCallback(FStringBuilderBase& OutResult)
|
||||
: Func([&OutResult](const FString& Msg) { OutResult.Reset(); OutResult.Appendf(TEXT("ERROR: %s\n"), *Msg); })
|
||||
{}
|
||||
|
||||
// ============================================================
|
||||
// Name Formatting
|
||||
// ============================================================
|
||||
|
||||
void MCPUtils::SanitizeNameInPlace(FString &Name)
|
||||
{
|
||||
int32 Dst = 0;
|
||||
for (int32 Src = 0; Src < Name.Len(); Src++)
|
||||
{
|
||||
TCHAR c = Name[Src];
|
||||
if (c <= 0x20 || c == '_' || c == 0x7F) continue;
|
||||
if (c >= 0x21 && c <= 0x7E && !FChar::IsAlnum(c))
|
||||
Name[Dst++] = '_';
|
||||
else
|
||||
Name[Dst++] = c;
|
||||
}
|
||||
Name.LeftInline(Dst);
|
||||
if (Name.IsEmpty()) Name = TEXT("_");
|
||||
}
|
||||
|
||||
void MCPUtils::AppendNumericSuffix(FString &Name, int32 N)
|
||||
{
|
||||
if (N > 0)
|
||||
Name += FString::Printf(TEXT("_%d"), N - 1);
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatName(UWorld *World)
|
||||
{
|
||||
return World->GetPathName();
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatName(UBlueprint *BP)
|
||||
{
|
||||
return BP->GetPathName();
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatName(UActorComponent *C)
|
||||
{
|
||||
return C->GetName();
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatName(UEdGraph *Graph)
|
||||
{
|
||||
FString Name = Graph->GetName();
|
||||
SanitizeNameInPlace(Name);
|
||||
return Name;
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatName(UEdGraphNode* Node)
|
||||
{
|
||||
// Sanitized first line of the node title.
|
||||
FString Title = Node->GetNodeTitle(ENodeTitleType::FullTitle).ToString();
|
||||
int32 NewlineIdx;
|
||||
if (Title.FindChar(TEXT('\n'), NewlineIdx))
|
||||
Title.LeftInline(NewlineIdx);
|
||||
SanitizeNameInPlace(Title);
|
||||
AppendNumericSuffix(Title, Node->GetFName().GetNumber());
|
||||
return Title;
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatName(UEdGraphPin *Pin)
|
||||
{
|
||||
FString Name = Pin->PinName.ToString();
|
||||
SanitizeNameInPlace(Name);
|
||||
return Name;
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatName(const FMemberReference &Ref)
|
||||
{
|
||||
FString Name = Ref.GetMemberName().ToString();
|
||||
SanitizeNameInPlace(Name);
|
||||
return Name;
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatName(const FBPVariableDescription &Var)
|
||||
{
|
||||
FString Name = Var.VarName.ToString();
|
||||
SanitizeNameInPlace(Name);
|
||||
return Name;
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatName(const UClass *Class)
|
||||
{
|
||||
FString Name = Class->GetName();
|
||||
SanitizeNameInPlace(Name);
|
||||
return Name;
|
||||
}
|
||||
|
||||
bool MCPUtils::Identifies(const FString &Name, const UClass *Class)
|
||||
{
|
||||
return FormatName(Class).Equals(Name, ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Identifies
|
||||
// ============================================================
|
||||
|
||||
bool MCPUtils::Identifies(const FString &Name, UWorld *World)
|
||||
{
|
||||
return FormatName(World).Equals(Name, ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
bool MCPUtils::Identifies(const FString &Name, UBlueprint *BP)
|
||||
{
|
||||
return FormatName(BP).Equals(Name, ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
bool MCPUtils::Identifies(const FString &Name, UActorComponent *C)
|
||||
{
|
||||
return FormatName(C).Equals(Name, ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
bool MCPUtils::Identifies(const FString &Name, UEdGraph *Graph)
|
||||
{
|
||||
return FormatName(Graph).Equals(Name, ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
bool MCPUtils::Identifies(const FString &Name, UEdGraphNode* Node)
|
||||
{
|
||||
if (Node->NodeGuid.ToString().Equals(Name, ESearchCase::IgnoreCase))
|
||||
return true;
|
||||
return FormatName(Node).Equals(Name, ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
bool MCPUtils::Identifies(const FString &Name, UEdGraphPin *Pin)
|
||||
{
|
||||
return FormatName(Pin).Equals(Name, ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
bool MCPUtils::Identifies(const FString &Name, const FMemberReference &Ref)
|
||||
{
|
||||
return FormatName(Ref).Equals(Name, ESearchCase::IgnoreCase);
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatPinType(const FEdGraphPinType& PinType)
|
||||
{
|
||||
if (UObject* SubObj = PinType.PinSubCategoryObject.Get())
|
||||
{
|
||||
return SubObj->GetName();
|
||||
}
|
||||
FString Type = PinType.PinCategory.ToString();
|
||||
Type[0] = FChar::ToUpper(Type[0]);
|
||||
return Type;
|
||||
}
|
||||
|
||||
FString MCPUtils::FormatPinType(UEdGraphPin* Pin)
|
||||
{
|
||||
return FormatPinType(Pin->PinType);
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// JSON helpers
|
||||
// ============================================================
|
||||
@@ -204,7 +358,7 @@ TArray<UEdGraph*> MCPUtils::AllGraphsNamed(UBlueprint* BP, const FString& Name)
|
||||
{
|
||||
TArray<UEdGraph*> Result;
|
||||
for (UEdGraph* Graph : AllGraphs(BP))
|
||||
if (Graph->GetName().Equals(Name, ESearchCase::IgnoreCase))
|
||||
if (Identifies(Name, Graph))
|
||||
Result.Add(Graph);
|
||||
return Result;
|
||||
}
|
||||
@@ -221,7 +375,7 @@ TArray<TSharedPtr<FJsonValue>> MCPUtils::AllGraphNamesJson(UBlueprint* BP)
|
||||
{
|
||||
TArray<TSharedPtr<FJsonValue>> Result;
|
||||
for (UEdGraph* Graph : AllGraphs(BP))
|
||||
Result.Add(MakeShared<FJsonValueString>(Graph->GetName()));
|
||||
Result.Add(MakeShared<FJsonValueString>(FormatName(Graph)));
|
||||
return Result;
|
||||
}
|
||||
|
||||
|
||||
11
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/paths.md
Normal file
11
Plugins/BlueprintMCP/Source/BlueprintMCP/Private/paths.md
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
Fetches a graph pin:
|
||||
|
||||
/Game/Widgets/WB_Hotkeys,graph:ReadLuaConfiguration,node:Self_Reference_03,pin:Result
|
||||
|
||||
Fetches an skeletal mesh:
|
||||
|
||||
/Game/Tangibles/TAN_Character,component:CharacterMesh0,property:SkeletalMeshAsset
|
||||
|
||||
|
||||
|
||||
@@ -21,25 +21,6 @@ private:
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
// Sanitize a name: trim whitespace, and replace space
|
||||
// with underscores.
|
||||
//
|
||||
static FString SanitizeName(const FString& Title);
|
||||
|
||||
// Get the pin type as a string. This is lossy,
|
||||
// we don't differentiate between object reference
|
||||
// or struct, for example. But that's OK, we don't
|
||||
// need precise type info, we just need readability.
|
||||
//
|
||||
static FString FormatPinType(const FEdGraphPinType& PinType);
|
||||
static FString FormatPinType(UEdGraphPin* Pin);
|
||||
|
||||
// Get the node base name as a sanitized string.
|
||||
// Later, we may append a number to this base name
|
||||
// in order to turn it into a unique string.
|
||||
//
|
||||
static FString FormatNodeBaseName(UEdGraphNode* Node);
|
||||
|
||||
// Get the pin that this pin is linked to. If the
|
||||
// pin is linked to multiple pins, returns the first.
|
||||
// If the pin is linked to a knot node, follow the
|
||||
@@ -69,32 +50,12 @@ private:
|
||||
//
|
||||
static UEdGraphPin* FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction);
|
||||
|
||||
// Given a sanitized pin display name or a sanitized pin
|
||||
// name, find the one pin that matches. If the string
|
||||
// provided doesn't match any pin, or if it matches
|
||||
// multiple pins ambiguously, returns nullptr. If the
|
||||
// string is the sanitized empty string, returns nullptr
|
||||
// even if that matches a pin.
|
||||
//
|
||||
static UEdGraphPin* BestMatchPin(UEdGraphNode* Node, EEdGraphPinDirection Direction, bool Exec, const FString& Name);
|
||||
|
||||
// Returns either the sanitized display name or
|
||||
// sanitized pin name. Chooses the one that
|
||||
// unambiguously identifies the pin. If neither is
|
||||
// ambiguous, prefers the display name. If both are
|
||||
// ambiguous, returns the display name with a question
|
||||
// mark prepended to indicate that it doesn't uniquely
|
||||
// identify the pin.
|
||||
//
|
||||
static FString FormatPinName(UEdGraphPin *Pin);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Traverse and Emit the Nodes.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
FString FormatNodeName(UEdGraphNode* Node);
|
||||
FString FormatPinSource(UEdGraphPin* Pin);
|
||||
void Traverse(UEdGraphNode* Node);
|
||||
void SortNodes();
|
||||
@@ -114,7 +75,6 @@ private:
|
||||
UEdGraph* Graph;
|
||||
|
||||
// Data populated by passes.
|
||||
TMap<UEdGraphNode*, FString> NodeNames;
|
||||
TArray<UEdGraphNode*> SortedNodes;
|
||||
TSet<UEdGraphNode*> Visited;
|
||||
|
||||
|
||||
80
Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h
Normal file
80
Plugins/BlueprintMCP/Source/BlueprintMCP/Public/MCPFetcher.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
#include "MCPUtils.h"
|
||||
|
||||
class UEdGraphPin;
|
||||
|
||||
// Resolves a path string into a UObject or UEdGraphPin.
|
||||
//
|
||||
// A path starts with a package path (e.g. "/Game/Widgets/WB_Hotkeys"),
|
||||
// followed by zero or more comma-separated walker segments of the form
|
||||
// "key:value". Each segment navigates from the current object to a child.
|
||||
//
|
||||
// Supported walkers:
|
||||
// graph:Name - find a named UEdGraph within a UBlueprint
|
||||
// node:Name - find a named UEdGraphNode within a UEdGraph
|
||||
// pin:Name - find a named UEdGraphPin on a UEdGraphNode
|
||||
// component:Name - find a named component in a Blueprint's SCS
|
||||
// property:Name - follow an object-valued UProperty to its value
|
||||
// levelblueprint - get the level blueprint from a UWorld
|
||||
//
|
||||
// Example paths:
|
||||
// /Game/Widgets/WB_Hotkeys,graph:ReadLuaConfiguration,node:Self_Reference_03,pin:Result
|
||||
// /Game/Tangibles/TAN_Character,component:CharacterMesh0,property:SkeletalMeshAsset
|
||||
//
|
||||
// Builder-style usage:
|
||||
// MCPFetcher F(cb);
|
||||
// if (!F.Walk(Path).Ok()) return;
|
||||
//
|
||||
// MCPFetcher F(cb, ExistingObj);
|
||||
// if (!F.Graph("EventGraph").Node("MyNode").Ok()) return;
|
||||
//
|
||||
class MCPFetcher
|
||||
{
|
||||
public:
|
||||
bool bError = false;
|
||||
UObject* Obj = nullptr;
|
||||
UEdGraphPin* ResultPin = nullptr;
|
||||
MCPErrorCallback ErrorCB = nullptr;
|
||||
|
||||
MCPFetcher(MCPErrorCallback CB) : ErrorCB(CB) {}
|
||||
MCPFetcher(MCPErrorCallback CB, UObject* O) : Obj(O), ErrorCB(CB) {}
|
||||
|
||||
// Walk one step.
|
||||
MCPFetcher& Graph(const FString& Value);
|
||||
MCPFetcher& Node(const FString& Value);
|
||||
MCPFetcher& Pin(const FString& Value);
|
||||
MCPFetcher& Component(const FString& Value);
|
||||
MCPFetcher& Property(const FString& Value);
|
||||
MCPFetcher& LevelBlueprint();
|
||||
|
||||
// Parse string and walk multiple steps.
|
||||
MCPFetcher& Walk(const FString& Path);
|
||||
|
||||
bool Ok() const { return !bError; }
|
||||
template<class T> T *Cast()
|
||||
{
|
||||
if (bError) return nullptr;
|
||||
T* Result = ::Cast<T>(Obj);
|
||||
if (Result == nullptr)
|
||||
TypeMismatch(TEXT("Cast"), *T::StaticClass()->GetName());
|
||||
return Result;
|
||||
}
|
||||
|
||||
private:
|
||||
static bool StrEq(const FString& A, const TCHAR* B) { return A.Equals(B, ESearchCase::IgnoreCase); }
|
||||
void SetObj(UObject* InObj) { Obj = InObj; ResultPin = nullptr; }
|
||||
void SetPin(UEdGraphPin* InPin) { ResultPin = InPin; Obj = nullptr; }
|
||||
MCPFetcher& SetError(const FString& Msg);
|
||||
MCPFetcher& TypeMismatch(const TCHAR* Walker, const TCHAR* Expected);
|
||||
void LoadUAsset(const FString& PackagePath);
|
||||
};
|
||||
|
||||
template<> inline UEdGraphPin* MCPFetcher::Cast<UEdGraphPin>()
|
||||
{
|
||||
if (bError) return nullptr;
|
||||
if (!ResultPin)
|
||||
TypeMismatch(TEXT("Cast"), TEXT("pin"));
|
||||
return ResultPin;
|
||||
}
|
||||
@@ -14,6 +14,10 @@ class UBlueprintNodeSpawner;
|
||||
class UAnimationStateMachineGraph;
|
||||
class UAnimStateNode;
|
||||
class UAnimStateTransitionNode;
|
||||
class UActorComponent;
|
||||
class UWorld;
|
||||
struct FMemberReference;
|
||||
struct FBPVariableDescription;
|
||||
|
||||
// ----- Log capture -----
|
||||
|
||||
@@ -87,6 +91,55 @@ struct MCPErrorCallback
|
||||
class MCPUtils
|
||||
{
|
||||
public:
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Name Formatting
|
||||
//
|
||||
// The goal here is to centralize the code that outputs
|
||||
// names, and have everybody use it, so that names are
|
||||
// used consistently. The secondary goal is to choose
|
||||
// names that are as uniquely-identifying as is practical.
|
||||
// It's not always 100% possible to get perfectly unique
|
||||
// names, though, so our code needs to check for ambiguity.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
static FString FormatName(UWorld *World);
|
||||
static FString FormatName(UBlueprint *BP);
|
||||
static FString FormatName(UActorComponent *C);
|
||||
static FString FormatName(UEdGraph *Graph);
|
||||
static FString FormatName(UEdGraphNode* Node);
|
||||
static FString FormatName(UEdGraphPin *Pin);
|
||||
static FString FormatName(const FMemberReference &Ref);
|
||||
static FString FormatName(const FBPVariableDescription &Var);
|
||||
static FString FormatName(const UClass *Class);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
//
|
||||
// Identifies
|
||||
//
|
||||
// Return true if the name identifies the object. The
|
||||
// FormatName functions, above, always return names that
|
||||
// identify the object. However, there may be other
|
||||
// names that also identify the object. Identifying names
|
||||
// aren't 100% guaranteed to be unique, but very likely.
|
||||
//
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
static bool Identifies(const FString &Name, UWorld *World);
|
||||
static bool Identifies(const FString &Name, UBlueprint *BP);
|
||||
static bool Identifies(const FString &Name, UActorComponent *C);
|
||||
static bool Identifies(const FString &Name, UEdGraph *Graph);
|
||||
static bool Identifies(const FString &Name, UEdGraphNode* Node);
|
||||
static bool Identifies(const FString &Name, UEdGraphPin *Pin);
|
||||
static bool Identifies(const FString &Name, const FMemberReference &Ref);
|
||||
static bool Identifies(const FString &Name, const UClass *Class);
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
static FString FormatPinType(const FEdGraphPinType& PinType);
|
||||
static FString FormatPinType(UEdGraphPin* Pin);
|
||||
|
||||
// ----- Asset path helpers -----
|
||||
// Splits "/Game/Foo/Bar" into PackagePath="/Game/Foo" and AssetName="Bar".
|
||||
// Returns false if the path has no slash or the asset name is empty.
|
||||
@@ -210,5 +263,7 @@ public:
|
||||
static void FormatCommandHelp(UClass* HandlerClass, FStringBuilderBase& Result);
|
||||
|
||||
private:
|
||||
static void SanitizeNameInPlace(FString& Name);
|
||||
static void AppendNumericSuffix(FString &Name, int32 N);
|
||||
static FString SetPropertyFromJson(void* Container, FProperty* Prop, const FString& FieldName, const FJsonObject* Json);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user