More work on blueprint exporter, and some work on build system
This commit is contained in:
@@ -109,9 +109,14 @@ Blueprints are automatically exported to readable text files in `Saved/Blueprint
|
|||||||
|
|
||||||
Do not use git to make changes (commit, push, branch, etc.). Read-only git commands (status, log, diff, etc.) are fine.
|
Do not use git to make changes (commit, push, branch, etc.). Read-only git commands (status, log, diff, etc.) are fine.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
## Coding Conventions
|
## Coding Conventions
|
||||||
|
|
||||||
- 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 or namespace-scoped functions instead.
|
||||||
|
- Use `LogLuprexIntegration` for log messages, not `LogTemp`.
|
||||||
- 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.
|
- 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
|
## Session Startup
|
||||||
|
|||||||
Binary file not shown.
@@ -87,14 +87,22 @@
|
|||||||
},
|
},
|
||||||
"problemMatcher": [
|
"problemMatcher": [
|
||||||
{
|
{
|
||||||
"base": "$gcc",
|
"owner": "build-luprex",
|
||||||
"source": "build.py",
|
"source": "build.py",
|
||||||
"fileLocation": ["relative", "${workspaceFolder}/luprex"]
|
"fileLocation": ["relative", "${workspaceFolder}/luprex"],
|
||||||
|
"pattern": {
|
||||||
|
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||||
|
"file": 1, "line": 2, "column": 3, "severity": 4, "message": 5
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"base": "$gcc",
|
"owner": "build-integration",
|
||||||
"source": "build.py",
|
"source": "build.py",
|
||||||
"fileLocation": ["relative", "${workspaceFolder}"]
|
"fileLocation": ["relative", "${workspaceFolder}"],
|
||||||
|
"pattern": {
|
||||||
|
"regexp": "^(.*):(\\d+):(\\d+):\\s+(?:fatal\\s+)?(warning|error):\\s+(.*)$",
|
||||||
|
"file": 1, "line": 2, "column": 3, "severity": 4, "message": 5
|
||||||
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"type": "shell"
|
"type": "shell"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
|
|
||||||
#include "BlueprintExporter.h"
|
#include "BlueprintExporter.h"
|
||||||
|
#include "Common.h"
|
||||||
#include "Engine/Blueprint.h"
|
#include "Engine/Blueprint.h"
|
||||||
#include "EdGraph/EdGraph.h"
|
#include "EdGraph/EdGraph.h"
|
||||||
#include "EdGraph/EdGraphNode.h"
|
#include "EdGraph/EdGraphNode.h"
|
||||||
@@ -20,123 +21,35 @@ FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph)
|
|||||||
EmitGraph();
|
EmitGraph();
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::AssignNodeNames()
|
////////////////////////////////////////////////////////
|
||||||
{
|
//
|
||||||
TMap<FString, int32> NextIndex;
|
// General utilities for manipulating UEdGraph nodes.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
for (UEdGraphNode* Node : SortedNodes)
|
FString FlxBlueprintExporter::SanitizeName(const FString& Title)
|
||||||
{
|
{
|
||||||
FString Base = SanitizeName(Node->GetNodeTitle(ENodeTitleType::ListView).ToString());
|
FString Result = Title.TrimStartAndEnd().Replace(TEXT(" "), TEXT("_"));
|
||||||
int32& Idx = NextIndex.FindOrAdd(Base, 0);
|
return Result.IsEmpty() ? TEXT("_") : Result;
|
||||||
FString Name = (Idx == 0) ? Base : FString::Printf(TEXT("%s_%d"), *Base, Idx + 1);
|
|
||||||
NodeNames.Add(Node, Name);
|
|
||||||
Idx++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::Traverse(UEdGraphNode* Node)
|
FString FlxBlueprintExporter::FormatPinType(UEdGraphPin* Pin)
|
||||||
{
|
{
|
||||||
if (Visited.Contains(Node)) return;
|
if (UObject* SubObj = Pin->PinType.PinSubCategoryObject.Get())
|
||||||
Visited.Add(Node);
|
|
||||||
|
|
||||||
// First, traverse input nodes
|
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
|
||||||
{
|
{
|
||||||
if (Pin->Direction != EGPD_Input) continue;
|
return SubObj->GetName();
|
||||||
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
|
||||||
{
|
|
||||||
Traverse(LinkedPin->GetOwningNode());
|
|
||||||
}
|
}
|
||||||
|
FString Type = Pin->PinType.PinCategory.ToString();
|
||||||
|
Type[0] = FChar::ToUpper(Type[0]);
|
||||||
|
return Type;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add this node to the sorted list.
|
FString FlxBlueprintExporter::FormatNodeBaseName(UEdGraphNode* Node)
|
||||||
SortedNodes.Add(Node);
|
|
||||||
|
|
||||||
// Then, traverse exec output nodes only.
|
|
||||||
// Data outputs are not followed — data nodes get pulled in
|
|
||||||
// through their consumers' input traversal.
|
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
|
||||||
{
|
{
|
||||||
if (Pin->Direction != EGPD_Output) continue;
|
return SanitizeName(Node->GetNodeTitle(ENodeTitleType::ListView).ToString());
|
||||||
if (Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) continue;
|
|
||||||
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
|
||||||
{
|
|
||||||
Traverse(LinkedPin->GetOwningNode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FlxBlueprintExporter::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
|
||||||
{
|
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
|
||||||
{
|
|
||||||
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
|
|
||||||
{
|
|
||||||
if (Pin->Direction == Direction) return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlxBlueprintExporter::SortNodes()
|
|
||||||
{
|
|
||||||
// Find starter nodes: have exec output but no exec input.
|
|
||||||
TArray<UEdGraphNode*> Starters;
|
|
||||||
for (UEdGraphNode* Node : Graph->Nodes)
|
|
||||||
{
|
|
||||||
if (HasExecPin(Node, EGPD_Output) && !HasExecPin(Node, EGPD_Input))
|
|
||||||
{
|
|
||||||
Starters.Add(Node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort starters by Y position.
|
|
||||||
Starters.Sort([](const UEdGraphNode& A, const UEdGraphNode& B)
|
|
||||||
{
|
|
||||||
return A.NodePosY < B.NodePosY;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Traverse from each starter.
|
|
||||||
for (UEdGraphNode* Starter : Starters)
|
|
||||||
{
|
|
||||||
Traverse(Starter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traverse all nodes.
|
|
||||||
for (UEdGraphNode* Node : Graph->Nodes)
|
|
||||||
{
|
|
||||||
Traverse(Node);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitNodeList()
|
|
||||||
{
|
|
||||||
Output.Appendf(TEXT("NodeList:\n"));
|
|
||||||
for (UEdGraphNode* Node : SortedNodes)
|
|
||||||
{
|
|
||||||
if (Node->IsA<UK2Node_Knot>()) continue;
|
|
||||||
if (Node->IsA<UEdGraphNode_Comment>()) continue;
|
|
||||||
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
|
||||||
Output.Appendf(TEXT(" %s = %s\n"),
|
|
||||||
*NodeNames[Node], *Node->NodeGuid.ToString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
FString FlxBlueprintExporter::GetPinDisplayName(UEdGraphPin *Pin)
|
|
||||||
{
|
|
||||||
FString Result = Pin->GetOwningNode()->GetPinDisplayName(Pin).ToString();
|
|
||||||
if (!Result.IsEmpty()) return Result;
|
|
||||||
return Pin->PinName.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
UEdGraphPin* FlxBlueprintExporter::FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
|
||||||
{
|
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
|
||||||
{
|
|
||||||
if (Pin->Direction == Direction) return Pin;
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
UEdGraphPin* FlxBlueprintExporter::GetLinkedTo(UEdGraphPin* Pin)
|
UEdGraphPin* FlxBlueprintExporter::GetLinkedTo(UEdGraphPin* Pin)
|
||||||
{
|
{
|
||||||
@@ -150,15 +63,116 @@ UEdGraphPin* FlxBlueprintExporter::GetLinkedTo(UEdGraphPin* Pin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FString FlxBlueprintExporter::FormatPinType(UEdGraphPin* Pin)
|
bool FlxBlueprintExporter::IsDefaultToSelf(UEdGraphPin* Pin)
|
||||||
{
|
{
|
||||||
if (UObject* SubObj = Pin->PinType.PinSubCategoryObject.Get())
|
// Only valid call-function nodes can have default-to-self.
|
||||||
{
|
UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Pin->GetOwningNode());
|
||||||
return SubObj->GetName();
|
if (!CallNode) return false;
|
||||||
|
UFunction* Function = CallNode->GetTargetFunction();
|
||||||
|
if (!Function) return false;
|
||||||
|
|
||||||
|
// In a call function node, the pin name 'self' is reserved
|
||||||
|
// for the C++ 'this' pointer. This always has 'DefaultToSelf'
|
||||||
|
// behavior. Note that 'self' in other nodes is not special.
|
||||||
|
if (Pin->PinName == UEdGraphSchema_K2::PN_Self) return true;
|
||||||
|
|
||||||
|
// For any other pin name, we have to check for
|
||||||
|
// the presence of meta = (DefaultToSelf = "Pin")
|
||||||
|
const FString& DefaultToSelfPinName = Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf);
|
||||||
|
return Pin->PinName.ToString() == DefaultToSelfPinName;
|
||||||
}
|
}
|
||||||
FString Type = Pin->PinType.PinCategory.ToString();
|
|
||||||
Type[0] = FChar::ToUpper(Type[0]);
|
TArray<UEdGraphPin*> FlxBlueprintExporter::FilterPins(UEdGraphNode* Node, EEdGraphPinDirection Direction, FName Category)
|
||||||
return Type;
|
{
|
||||||
|
TArray<UEdGraphPin*> Result;
|
||||||
|
for (UEdGraphPin* Pin : Node->Pins)
|
||||||
|
{
|
||||||
|
if (Direction != EGPD_MAX && Pin->Direction != Direction) continue;
|
||||||
|
if (!Category.IsNone() && Pin->PinType.PinCategory != Category) continue;
|
||||||
|
Result.Add(Pin);
|
||||||
|
}
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FlxBlueprintExporter::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
||||||
|
{
|
||||||
|
return FilterPins(Node, Direction, UEdGraphSchema_K2::PC_Exec).Num() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UEdGraphPin* FlxBlueprintExporter::FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction)
|
||||||
|
{
|
||||||
|
for (UEdGraphPin* Pin : Node->Pins)
|
||||||
|
{
|
||||||
|
if (Pin->Direction == Direction) return Pin;
|
||||||
|
}
|
||||||
|
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(LogLuprexIntegration, 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// Traverse and Emit the Nodes.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
FString FlxBlueprintExporter::FormatNodeName(UEdGraphNode* Node)
|
||||||
|
{
|
||||||
|
FString* Name = NodeNames.Find(Node);
|
||||||
|
return Name ? *Name : FormatNodeBaseName(Node);
|
||||||
}
|
}
|
||||||
|
|
||||||
FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
||||||
@@ -173,9 +187,8 @@ FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
|||||||
if (UK2Node_VariableGet* VarGet = Cast<UK2Node_VariableGet>(LinkedToNode))
|
if (UK2Node_VariableGet* VarGet = Cast<UK2Node_VariableGet>(LinkedToNode))
|
||||||
return SanitizeName(VarGet->GetVarNameString());
|
return SanitizeName(VarGet->GetVarNameString());
|
||||||
|
|
||||||
FString PinLabel = SanitizeName(GetPinDisplayName(LinkedTo));
|
FString PinLabel = FormatPinName(LinkedTo);
|
||||||
FString* Name = NodeNames.Find(LinkedToNode);
|
return FString::Printf(TEXT("%s.%s"), *FormatNodeName(LinkedToNode), *PinLabel);
|
||||||
return FString::Printf(TEXT("%s.%s"), **Name, *PinLabel);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// String pins: always show in quotes (even if empty).
|
// String pins: always show in quotes (even if empty).
|
||||||
@@ -213,6 +226,72 @@ FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FlxBlueprintExporter::Traverse(UEdGraphNode* Node)
|
||||||
|
{
|
||||||
|
if (Visited.Contains(Node)) return;
|
||||||
|
Visited.Add(Node);
|
||||||
|
|
||||||
|
// First, traverse input nodes
|
||||||
|
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
|
||||||
|
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
||||||
|
Traverse(LinkedPin->GetOwningNode());
|
||||||
|
|
||||||
|
// Add this node to the sorted list.
|
||||||
|
SortedNodes.Add(Node);
|
||||||
|
|
||||||
|
// Then, traverse exec output nodes only.
|
||||||
|
// Data outputs are not followed — data nodes get pulled in
|
||||||
|
// through their consumers' input traversal.
|
||||||
|
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Output, UEdGraphSchema_K2::PC_Exec))
|
||||||
|
for (UEdGraphPin* LinkedPin : Pin->LinkedTo)
|
||||||
|
Traverse(LinkedPin->GetOwningNode());
|
||||||
|
}
|
||||||
|
|
||||||
|
void FlxBlueprintExporter::SortNodes()
|
||||||
|
{
|
||||||
|
// Find starter nodes: have exec output but no exec input.
|
||||||
|
TArray<UEdGraphNode*> Starters;
|
||||||
|
for (UEdGraphNode* Node : Graph->Nodes)
|
||||||
|
{
|
||||||
|
if (HasExecPin(Node, EGPD_Output) && !HasExecPin(Node, EGPD_Input))
|
||||||
|
{
|
||||||
|
Starters.Add(Node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort starters by Y position.
|
||||||
|
Starters.Sort([](const UEdGraphNode& A, const UEdGraphNode& B)
|
||||||
|
{
|
||||||
|
return A.NodePosY < B.NodePosY;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Traverse from each starter.
|
||||||
|
for (UEdGraphNode* Starter : Starters)
|
||||||
|
{
|
||||||
|
Traverse(Starter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Traverse all nodes.
|
||||||
|
for (UEdGraphNode* Node : Graph->Nodes)
|
||||||
|
{
|
||||||
|
Traverse(Node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
||||||
{
|
{
|
||||||
if (Node->IsA<UEdGraphNode_Comment>())
|
if (Node->IsA<UEdGraphNode_Comment>())
|
||||||
@@ -221,123 +300,70 @@ void FlxBlueprintExporter::EmitNode(UEdGraphNode* Node)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Node->IsA<UK2Node_VariableGet>())
|
Output.Appendf(TEXT("\nnode %s\n"), *FormatNodeName(Node));
|
||||||
return;
|
|
||||||
|
|
||||||
Output.Appendf(TEXT("\n %s\n"), *NodeNames[Node]);
|
|
||||||
|
|
||||||
// Emit input data pins.
|
// Emit input data pins.
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Input))
|
||||||
{
|
{
|
||||||
if (Pin->Direction != EGPD_Input) continue;
|
|
||||||
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;
|
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;
|
||||||
if (Pin->bHidden) continue;
|
if (Pin->bHidden) continue;
|
||||||
|
|
||||||
Output.Appendf(TEXT(" %s %s = %s\n"),
|
Output.Appendf(TEXT(" input %s %s = %s\n"),
|
||||||
*FormatPinType(Pin),
|
*FormatPinType(Pin),
|
||||||
*SanitizeName(GetPinDisplayName(Pin)),
|
*FormatPinName(Pin),
|
||||||
*FormatPinSource(Pin));
|
*FormatPinSource(Pin));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit output data pins as a return line.
|
// Emit output data pins as a return line.
|
||||||
FString ReturnPins;
|
FString ReturnPins;
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
for (UEdGraphPin* Pin : FilterPins(Node, EGPD_Output))
|
||||||
{
|
{
|
||||||
if (Pin->Direction != EGPD_Output) continue;
|
|
||||||
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;
|
if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) continue;
|
||||||
if (Pin->bHidden) continue;
|
if (Pin->bHidden) continue;
|
||||||
if (!ReturnPins.IsEmpty()) ReturnPins += TEXT(", ");
|
if (!ReturnPins.IsEmpty()) ReturnPins += TEXT(", ");
|
||||||
ReturnPins += SanitizeName(GetPinDisplayName(Pin));
|
ReturnPins += FormatPinName(Pin);
|
||||||
}
|
}
|
||||||
if (!ReturnPins.IsEmpty())
|
if (!ReturnPins.IsEmpty())
|
||||||
Output.Appendf(TEXT(" return %s\n"), *ReturnPins);
|
|
||||||
|
|
||||||
// Detect output exec pin patterns.
|
|
||||||
int32 ExecOutCount = 0;
|
|
||||||
bool HasElsePin = false;
|
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
|
||||||
{
|
{
|
||||||
if (Pin->Direction != EGPD_Output) continue;
|
Output.Appendf(TEXT(" return %s\n"), *ReturnPins);
|
||||||
if (Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) continue;
|
|
||||||
ExecOutCount++;
|
|
||||||
if (Pin->PinName == TEXT("Else"))
|
|
||||||
HasElsePin = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit output exec pins as goto statements.
|
// Emit output exec pins as goto statements.
|
||||||
for (UEdGraphPin* Pin : Node->Pins)
|
TArray<UEdGraphPin*> ExecOuts = FilterPins(Node, EGPD_Output, UEdGraphSchema_K2::PC_Exec);
|
||||||
|
for (UEdGraphPin* Pin : ExecOuts)
|
||||||
{
|
{
|
||||||
if (Pin->Direction != EGPD_Output) continue;
|
|
||||||
if (Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) continue;
|
|
||||||
UEdGraphPin* LinkedTo = GetLinkedTo(Pin);
|
UEdGraphPin* LinkedTo = GetLinkedTo(Pin);
|
||||||
if (!LinkedTo) continue;
|
if (!LinkedTo) continue;
|
||||||
|
|
||||||
FString* TargetName = NodeNames.Find(LinkedTo->GetOwningNode());
|
FString Target = FormatNodeName(LinkedTo->GetOwningNode());
|
||||||
FString Target = TargetName ? *TargetName : TEXT("?");
|
|
||||||
|
|
||||||
if (ExecOutCount == 1)
|
if (ExecOuts.Num() == 1)
|
||||||
Output.Appendf(TEXT(" goto %s\n"), *Target);
|
Output.Appendf(TEXT(" goto %s\n"), *Target);
|
||||||
else if (HasElsePin)
|
|
||||||
Output.Appendf(TEXT(" %s goto %s\n"), *Pin->PinName.ToString().ToLower(), *Target);
|
|
||||||
else
|
else
|
||||||
Output.Appendf(TEXT(" goto %s if %s\n"), *Target, *Pin->PinName.ToString());
|
Output.Appendf(TEXT(" goto-if %s %s\n"), *FormatPinName(Pin), *Target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FlxBlueprintExporter::EmitGraph()
|
void FlxBlueprintExporter::EmitGraph()
|
||||||
{
|
{
|
||||||
Output.Appendf(TEXT("\nGraph:\n"));
|
|
||||||
for (UEdGraphNode* Node : SortedNodes)
|
for (UEdGraphNode* Node : SortedNodes)
|
||||||
{
|
{
|
||||||
if (Node->IsA<UK2Node_Knot>()) continue;
|
if (Node->IsA<UK2Node_Knot>()) continue;
|
||||||
|
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
||||||
EmitNode(Node);
|
EmitNode(Node);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FlxBlueprintExporter::IsDefaultToSelf(UEdGraphPin* Pin)
|
void FlxBlueprintExporter::EmitNodeList()
|
||||||
{
|
{
|
||||||
// The engine's own IsSelfPin check.
|
for (UEdGraphNode* Node : SortedNodes)
|
||||||
if (Pin->PinName == UEdGraphSchema_K2::PN_Self)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
// Check if the owning node is a function call with DefaultToSelf metadata naming this pin.
|
|
||||||
UK2Node_CallFunction* CallNode = Cast<UK2Node_CallFunction>(Pin->GetOwningNode());
|
|
||||||
if (!CallNode) return false;
|
|
||||||
|
|
||||||
UFunction* Function = CallNode->GetTargetFunction();
|
|
||||||
if (!Function) return false;
|
|
||||||
|
|
||||||
const FString& DefaultToSelfPinName = Function->GetMetaData(FBlueprintMetadata::MD_DefaultToSelf);
|
|
||||||
return Pin->PinName.ToString() == DefaultToSelfPinName;
|
|
||||||
}
|
|
||||||
|
|
||||||
FString FlxBlueprintExporter::SanitizeName(const FString& Title)
|
|
||||||
{
|
{
|
||||||
FString Result;
|
if (Node->IsA<UK2Node_Knot>()) continue;
|
||||||
bool PrevWasUnderscore = true; // suppress leading underscore
|
if (Node->IsA<UEdGraphNode_Comment>()) continue;
|
||||||
for (TCHAR Ch : Title)
|
if (Node->IsA<UK2Node_VariableGet>()) continue;
|
||||||
{
|
Details.Appendf(TEXT("%s = %s\n"),
|
||||||
if (FChar::IsWhitespace(Ch))
|
*FormatNodeName(Node), *Node->NodeGuid.ToString());
|
||||||
{
|
|
||||||
if (!PrevWasUnderscore)
|
|
||||||
{
|
|
||||||
Result += TEXT('_');
|
|
||||||
PrevWasUnderscore = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Result += Ch;
|
|
||||||
PrevWasUnderscore = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Trim trailing underscore
|
|
||||||
if (Result.Len() > 0 && Result[Result.Len() - 1] == TEXT('_'))
|
|
||||||
{
|
|
||||||
Result.LeftChopInline(1);
|
|
||||||
}
|
|
||||||
return Result.IsEmpty() ? TEXT("_") : Result;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "Engine/Blueprint.h"
|
||||||
class UBlueprint;
|
#include "EdGraph/EdGraph.h"
|
||||||
class UEdGraph;
|
#include "EdGraph/EdGraphNode.h"
|
||||||
class UEdGraphNode;
|
#include "EdGraph/EdGraphPin.h"
|
||||||
|
|
||||||
class FlxBlueprintExporter
|
class FlxBlueprintExporter
|
||||||
{
|
{
|
||||||
@@ -14,22 +14,102 @@ public:
|
|||||||
FlxBlueprintExporter(UEdGraph* InGraph);
|
FlxBlueprintExporter(UEdGraph* InGraph);
|
||||||
|
|
||||||
const FString GetOutput() { return Output.ToString(); }
|
const FString GetOutput() { return Output.ToString(); }
|
||||||
|
const FString GetDetails() { return Details.ToString(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
// General utilities for manipulating UEdGraph nodes.
|
||||||
|
//
|
||||||
|
////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// 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(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
|
||||||
|
// chain of knot nodes and find the pin at the other
|
||||||
|
// end of the chain. Returns nullptr if this pin
|
||||||
|
// is not linked to anything.
|
||||||
|
//
|
||||||
|
static UEdGraphPin* GetLinkedTo(UEdGraphPin *Pin);
|
||||||
|
|
||||||
|
// Return true if the pin in question defaults
|
||||||
|
// to self.
|
||||||
|
//
|
||||||
|
static bool IsDefaultToSelf(UEdGraphPin* Pin);
|
||||||
|
|
||||||
|
// Get a subset of the pins in the node, filtered
|
||||||
|
// by direction, category, or both.
|
||||||
|
//
|
||||||
|
static TArray<UEdGraphPin*> FilterPins(UEdGraphNode* Node,
|
||||||
|
EEdGraphPinDirection Direction = EGPD_MAX, FName Category = FName());
|
||||||
|
|
||||||
|
// Return true if the node has an exec pin that points
|
||||||
|
// in the specified direction.
|
||||||
|
//
|
||||||
|
static bool HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction);
|
||||||
|
|
||||||
|
// Find the first pin that points in the specified direction.
|
||||||
|
//
|
||||||
|
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();
|
void SortNodes();
|
||||||
void AssignNodeNames();
|
void AssignNodeNames();
|
||||||
void EmitNodeList();
|
|
||||||
void EmitGraph();
|
|
||||||
void EmitNode(UEdGraphNode* Node);
|
void EmitNode(UEdGraphNode* Node);
|
||||||
void Traverse(UEdGraphNode* Node);
|
void EmitGraph();
|
||||||
bool HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction);
|
void EmitNodeList();
|
||||||
UEdGraphPin* FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction);
|
|
||||||
bool IsDefaultToSelf(UEdGraphPin* Pin);
|
////////////////////////////////////////////////////////
|
||||||
FString SanitizeName(const FString& Title);
|
//
|
||||||
FString FormatPinType(UEdGraphPin* Pin);
|
// Values recorded during traversal.
|
||||||
FString FormatPinSource(UEdGraphPin* Pin);
|
//
|
||||||
UEdGraphPin* GetLinkedTo(UEdGraphPin *Pin);
|
////////////////////////////////////////////////////////
|
||||||
FString GetPinDisplayName(UEdGraphPin *Pin);
|
|
||||||
|
|
||||||
UEdGraph* Graph;
|
UEdGraph* Graph;
|
||||||
|
|
||||||
@@ -38,8 +118,9 @@ private:
|
|||||||
TArray<UEdGraphNode*> SortedNodes;
|
TArray<UEdGraphNode*> SortedNodes;
|
||||||
TSet<UEdGraphNode*> Visited;
|
TSet<UEdGraphNode*> Visited;
|
||||||
|
|
||||||
// Output buffer.
|
// Output buffers.
|
||||||
TStringBuilder<4096> Output;
|
TStringBuilder<4096> Output;
|
||||||
|
TStringBuilder<4096> Details;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright Epic Games, Inc. All Rights Reserved.
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||||||
|
|
||||||
#include "Integration.h"
|
#include "Integration.h"
|
||||||
|
#include "Common.h"
|
||||||
#include "Modules/ModuleManager.h"
|
#include "Modules/ModuleManager.h"
|
||||||
|
|
||||||
#if WITH_EDITOR
|
#if WITH_EDITOR
|
||||||
@@ -40,6 +41,8 @@ void FlxIntegrationModuleImpl::OnAssetSaved(const FString& PackageFilename, UPac
|
|||||||
{
|
{
|
||||||
FString BPDir = FPaths::ProjectDir() / TEXT("Saved") / TEXT("BlueprintExports") / BP->GetName();
|
FString BPDir = FPaths::ProjectDir() / TEXT("Saved") / TEXT("BlueprintExports") / BP->GetName();
|
||||||
|
|
||||||
|
IFileManager::Get().DeleteDirectory(*BPDir, false, true);
|
||||||
|
|
||||||
TArray<UEdGraph*> AllGraphs;
|
TArray<UEdGraph*> AllGraphs;
|
||||||
BP->GetAllGraphs(AllGraphs);
|
BP->GetAllGraphs(AllGraphs);
|
||||||
|
|
||||||
@@ -48,8 +51,10 @@ void FlxIntegrationModuleImpl::OnAssetSaved(const FString& PackageFilename, UPac
|
|||||||
FlxBlueprintExporter Exporter(Graph);
|
FlxBlueprintExporter Exporter(Graph);
|
||||||
|
|
||||||
FString FilePath = BPDir / Graph->GetName() + TEXT(".txt");
|
FString FilePath = BPDir / Graph->GetName() + TEXT(".txt");
|
||||||
|
FString DetailsPath = BPDir / TEXT("DETAILS") / Graph->GetName() + TEXT(".txt");
|
||||||
FFileHelper::SaveStringToFile(Exporter.GetOutput(), *FilePath);
|
FFileHelper::SaveStringToFile(Exporter.GetOutput(), *FilePath);
|
||||||
UE_LOG(LogTemp, Warning, TEXT("Blueprint export: %s"), *FilePath);
|
FFileHelper::SaveStringToFile(Exporter.GetDetails(), *DetailsPath);
|
||||||
|
UE_LOG(LogLuprexIntegration, Warning, TEXT("Blueprint export: %s"), *FilePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "CoreMinimal.h"
|
#include "CoreMinimal.h"
|
||||||
|
#include "Modules/ModuleInterface.h"
|
||||||
|
#include "UObject/ObjectSaveContext.h"
|
||||||
|
|
||||||
class FlxIntegrationModuleImpl : public IModuleInterface
|
class FlxIntegrationModuleImpl : public IModuleInterface
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
#include "LuaCallNode.h"
|
#include "LuaCallNode.h"
|
||||||
#include "StringDecoder.h"
|
#include "StringDecoder.h"
|
||||||
|
|
||||||
|
#include "GameFramework/Actor.h"
|
||||||
#include "BlueprintActionDatabaseRegistrar.h"
|
#include "BlueprintActionDatabaseRegistrar.h"
|
||||||
#include "BlueprintNodeSpawner.h"
|
#include "BlueprintNodeSpawner.h"
|
||||||
#include "Containers/EnumAsByte.h"
|
#include "Containers/EnumAsByte.h"
|
||||||
|
|||||||
5
build.py
5
build.py
@@ -144,7 +144,7 @@ def get_build_mode_from_command_line():
|
|||||||
"""
|
"""
|
||||||
mode = sys.argv[1].lower() if len(sys.argv) > 1 else 'all'
|
mode = sys.argv[1].lower() if len(sys.argv) > 1 else 'all'
|
||||||
if mode in ["cpp", "cxx"]: mode = "c++"
|
if mode in ["cpp", "cxx"]: mode = "c++"
|
||||||
if not mode in ["all", "c++", "clean", "ccjson"]:
|
if not mode in ["all", "c++", "clean", "ccjson", "code-workspace"]:
|
||||||
sys.exit(f"Invalid build mode: {mode}")
|
sys.exit(f"Invalid build mode: {mode}")
|
||||||
return mode
|
return mode
|
||||||
|
|
||||||
@@ -400,6 +400,9 @@ os.chdir(f"{INTEGRATION}/EnginePatches")
|
|||||||
if MODE == "ccjson":
|
if MODE == "ccjson":
|
||||||
build_intellisense_database_for_clangd()
|
build_intellisense_database_for_clangd()
|
||||||
|
|
||||||
|
if MODE == "code-workspace":
|
||||||
|
generate_integration_code_workspace()
|
||||||
|
|
||||||
if MODE == "all":
|
if MODE == "all":
|
||||||
unzip_unreal_engine_and_apply_patch()
|
unzip_unreal_engine_and_apply_patch()
|
||||||
generate_buildconfiguration_xml()
|
generate_buildconfiguration_xml()
|
||||||
|
|||||||
Reference in New Issue
Block a user