diff --git a/CLAUDE.md b/CLAUDE.md index 0f91e0a6..3229fc41 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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. +## 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 - 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. ## Session Startup diff --git a/Content/Tangibles/TAN_Character.uasset b/Content/Tangibles/TAN_Character.uasset index 89dfc322..1d6a3406 100644 --- a/Content/Tangibles/TAN_Character.uasset +++ b/Content/Tangibles/TAN_Character.uasset @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1aba9d4990235d635136e3591c579bb1b5f640104b33d9b0fe00a96b518e4ac +oid sha256:5d217aee4204290fcb79f9e1b45697d4f8b6f8a9af3908eeb88491035b76e2ce size 353527 diff --git a/Integration.code-workspace.tpl.json b/Integration.code-workspace.tpl.json index 1c33f25f..6b3b0b79 100644 --- a/Integration.code-workspace.tpl.json +++ b/Integration.code-workspace.tpl.json @@ -87,14 +87,22 @@ }, "problemMatcher": [ { - "base": "$gcc", - "source": "build.py", - "fileLocation": ["relative", "${workspaceFolder}/luprex"] + "owner": "build-luprex", + "source": "build.py", + "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", - "source": "build.py", - "fileLocation": ["relative", "${workspaceFolder}"] + "owner": "build-integration", + "source": "build.py", + "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" diff --git a/Source/Integration/BlueprintExporter.cpp b/Source/Integration/BlueprintExporter.cpp index 7754ab46..2a3baf28 100644 --- a/Source/Integration/BlueprintExporter.cpp +++ b/Source/Integration/BlueprintExporter.cpp @@ -1,6 +1,7 @@ #if WITH_EDITOR #include "BlueprintExporter.h" +#include "Common.h" #include "Engine/Blueprint.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" @@ -20,123 +21,35 @@ FlxBlueprintExporter::FlxBlueprintExporter(UEdGraph* InGraph) EmitGraph(); } -void FlxBlueprintExporter::AssignNodeNames() -{ - TMap NextIndex; +//////////////////////////////////////////////////////// +// +// General utilities for manipulating UEdGraph nodes. +// +//////////////////////////////////////////////////////// - for (UEdGraphNode* Node : SortedNodes) - { - FString Base = SanitizeName(Node->GetNodeTitle(ENodeTitleType::ListView).ToString()); - int32& Idx = NextIndex.FindOrAdd(Base, 0); - FString Name = (Idx == 0) ? Base : FString::Printf(TEXT("%s_%d"), *Base, Idx + 1); - NodeNames.Add(Node, Name); - Idx++; - } +FString FlxBlueprintExporter::SanitizeName(const FString& Title) +{ + FString Result = Title.TrimStartAndEnd().Replace(TEXT(" "), TEXT("_")); + return Result.IsEmpty() ? TEXT("_") : Result; } -void FlxBlueprintExporter::Traverse(UEdGraphNode* Node) +FString FlxBlueprintExporter::FormatPinType(UEdGraphPin* Pin) { - if (Visited.Contains(Node)) return; - Visited.Add(Node); - - // First, traverse input nodes - for (UEdGraphPin* Pin : Node->Pins) + if (UObject* SubObj = Pin->PinType.PinSubCategoryObject.Get()) { - if (Pin->Direction != EGPD_Input) continue; - 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 : Node->Pins) - { - if (Pin->Direction != EGPD_Output) continue; - if (Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) continue; - for (UEdGraphPin* LinkedPin : Pin->LinkedTo) - { - Traverse(LinkedPin->GetOwningNode()); - } + return SubObj->GetName(); } + FString Type = Pin->PinType.PinCategory.ToString(); + Type[0] = FChar::ToUpper(Type[0]); + return Type; } -bool FlxBlueprintExporter::HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction) +FString FlxBlueprintExporter::FormatNodeBaseName(UEdGraphNode* Node) { - for (UEdGraphPin* Pin : Node->Pins) - { - if (Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec) - { - if (Pin->Direction == Direction) return true; - } - } - return false; + return SanitizeName(Node->GetNodeTitle(ENodeTitleType::ListView).ToString()); } -void FlxBlueprintExporter::SortNodes() -{ - // Find starter nodes: have exec output but no exec input. - TArray 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()) continue; - if (Node->IsA()) continue; - if (Node->IsA()) 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) { @@ -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(Pin->GetOwningNode()); + 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; +} + +TArray FlxBlueprintExporter::FilterPins(UEdGraphNode* Node, EEdGraphPinDirection Direction, FName Category) +{ + TArray Result; + for (UEdGraphPin* Pin : Node->Pins) { - return SubObj->GetName(); + if (Direction != EGPD_MAX && Pin->Direction != Direction) continue; + if (!Category.IsNone() && Pin->PinType.PinCategory != Category) continue; + Result.Add(Pin); } - FString Type = Pin->PinType.PinCategory.ToString(); - Type[0] = FChar::ToUpper(Type[0]); - return Type; + 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) @@ -173,9 +187,8 @@ FString FlxBlueprintExporter::FormatPinSource(UEdGraphPin* Pin) if (UK2Node_VariableGet* VarGet = Cast(LinkedToNode)) return SanitizeName(VarGet->GetVarNameString()); - FString PinLabel = SanitizeName(GetPinDisplayName(LinkedTo)); - FString* Name = NodeNames.Find(LinkedToNode); - return FString::Printf(TEXT("%s.%s"), **Name, *PinLabel); + FString PinLabel = FormatPinName(LinkedTo); + return FString::Printf(TEXT("%s.%s"), *FormatNodeName(LinkedToNode), *PinLabel); } // String pins: always show in quotes (even if empty). @@ -213,131 +226,144 @@ 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 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 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) { if (Node->IsA()) { - Output.Appendf(TEXT("\n // %s\n"), *Node->NodeComment); + Output.Appendf(TEXT("\n// %s\n"), *Node->NodeComment); return; } - if (Node->IsA()) - return; - - Output.Appendf(TEXT("\n %s\n"), *NodeNames[Node]); + Output.Appendf(TEXT("\nnode %s\n"), *FormatNodeName(Node)); // 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->bHidden) continue; - Output.Appendf(TEXT(" %s %s = %s\n"), + Output.Appendf(TEXT(" input %s %s = %s\n"), *FormatPinType(Pin), - *SanitizeName(GetPinDisplayName(Pin)), + *FormatPinName(Pin), *FormatPinSource(Pin)); } // Emit output data pins as a return line. 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->bHidden) continue; - if (!ReturnPins.IsEmpty()) ReturnPins += TEXT(","); - ReturnPins += SanitizeName(GetPinDisplayName(Pin)); + if (!ReturnPins.IsEmpty()) ReturnPins += TEXT(", "); + ReturnPins += FormatPinName(Pin); } 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; - if (Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec) continue; - ExecOutCount++; - if (Pin->PinName == TEXT("Else")) - HasElsePin = true; + Output.Appendf(TEXT(" return %s\n"), *ReturnPins); } // Emit output exec pins as goto statements. - for (UEdGraphPin* Pin : Node->Pins) + TArray 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); if (!LinkedTo) continue; - FString* TargetName = NodeNames.Find(LinkedTo->GetOwningNode()); - FString Target = TargetName ? *TargetName : TEXT("?"); + FString Target = FormatNodeName(LinkedTo->GetOwningNode()); - if (ExecOutCount == 1) + if (ExecOuts.Num() == 1) Output.Appendf(TEXT(" goto %s\n"), *Target); - else if (HasElsePin) - Output.Appendf(TEXT(" %s goto %s\n"), *Pin->PinName.ToString().ToLower(), *Target); 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() { - Output.Appendf(TEXT("\nGraph:\n")); for (UEdGraphNode* Node : SortedNodes) { if (Node->IsA()) continue; + if (Node->IsA()) continue; EmitNode(Node); } } -bool FlxBlueprintExporter::IsDefaultToSelf(UEdGraphPin* Pin) +void FlxBlueprintExporter::EmitNodeList() { - // The engine's own IsSelfPin check. - 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(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; - bool PrevWasUnderscore = true; // suppress leading underscore - for (TCHAR Ch : Title) + for (UEdGraphNode* Node : SortedNodes) { - if (FChar::IsWhitespace(Ch)) - { - if (!PrevWasUnderscore) - { - Result += TEXT('_'); - PrevWasUnderscore = true; - } - } - else - { - Result += Ch; - PrevWasUnderscore = false; - } + if (Node->IsA()) continue; + if (Node->IsA()) continue; + if (Node->IsA()) continue; + Details.Appendf(TEXT("%s = %s\n"), + *FormatNodeName(Node), *Node->NodeGuid.ToString()); } - // Trim trailing underscore - if (Result.Len() > 0 && Result[Result.Len() - 1] == TEXT('_')) - { - Result.LeftChopInline(1); - } - return Result.IsEmpty() ? TEXT("_") : Result; } - #endif diff --git a/Source/Integration/BlueprintExporter.h b/Source/Integration/BlueprintExporter.h index 98539d79..8e01e036 100644 --- a/Source/Integration/BlueprintExporter.h +++ b/Source/Integration/BlueprintExporter.h @@ -3,10 +3,10 @@ #if WITH_EDITOR #include "CoreMinimal.h" - -class UBlueprint; -class UEdGraph; -class UEdGraphNode; +#include "Engine/Blueprint.h" +#include "EdGraph/EdGraph.h" +#include "EdGraph/EdGraphNode.h" +#include "EdGraph/EdGraphPin.h" class FlxBlueprintExporter { @@ -14,22 +14,102 @@ public: FlxBlueprintExporter(UEdGraph* InGraph); const FString GetOutput() { return Output.ToString(); } + const FString GetDetails() { return Details.ToString(); } 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 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 AssignNodeNames(); - void EmitNodeList(); - void EmitGraph(); void EmitNode(UEdGraphNode* Node); - void Traverse(UEdGraphNode* Node); - bool HasExecPin(UEdGraphNode* Node, EEdGraphPinDirection Direction); - UEdGraphPin* FindFirstPin(UEdGraphNode* Node, EEdGraphPinDirection Direction); - bool IsDefaultToSelf(UEdGraphPin* Pin); - FString SanitizeName(const FString& Title); - FString FormatPinType(UEdGraphPin* Pin); - FString FormatPinSource(UEdGraphPin* Pin); - UEdGraphPin* GetLinkedTo(UEdGraphPin *Pin); - FString GetPinDisplayName(UEdGraphPin *Pin); + void EmitGraph(); + void EmitNodeList(); + + //////////////////////////////////////////////////////// + // + // Values recorded during traversal. + // + //////////////////////////////////////////////////////// + UEdGraph* Graph; @@ -38,8 +118,9 @@ private: TArray SortedNodes; TSet Visited; - // Output buffer. + // Output buffers. TStringBuilder<4096> Output; + TStringBuilder<4096> Details; }; #endif diff --git a/Source/Integration/Integration.cpp b/Source/Integration/Integration.cpp index 62e417af..e5df9503 100644 --- a/Source/Integration/Integration.cpp +++ b/Source/Integration/Integration.cpp @@ -1,6 +1,7 @@ // Copyright Epic Games, Inc. All Rights Reserved. #include "Integration.h" +#include "Common.h" #include "Modules/ModuleManager.h" #if WITH_EDITOR @@ -40,6 +41,8 @@ void FlxIntegrationModuleImpl::OnAssetSaved(const FString& PackageFilename, UPac { FString BPDir = FPaths::ProjectDir() / TEXT("Saved") / TEXT("BlueprintExports") / BP->GetName(); + IFileManager::Get().DeleteDirectory(*BPDir, false, true); + TArray AllGraphs; BP->GetAllGraphs(AllGraphs); @@ -48,8 +51,10 @@ void FlxIntegrationModuleImpl::OnAssetSaved(const FString& PackageFilename, UPac FlxBlueprintExporter Exporter(Graph); FString FilePath = BPDir / Graph->GetName() + TEXT(".txt"); + FString DetailsPath = BPDir / TEXT("DETAILS") / Graph->GetName() + TEXT(".txt"); 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; diff --git a/Source/Integration/Integration.h b/Source/Integration/Integration.h index 3855be13..c1c6b8d8 100644 --- a/Source/Integration/Integration.h +++ b/Source/Integration/Integration.h @@ -3,6 +3,8 @@ #pragma once #include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "UObject/ObjectSaveContext.h" class FlxIntegrationModuleImpl : public IModuleInterface { diff --git a/Source/Integration/LuaCallNode.cpp b/Source/Integration/LuaCallNode.cpp index ad647c91..30a031bd 100644 --- a/Source/Integration/LuaCallNode.cpp +++ b/Source/Integration/LuaCallNode.cpp @@ -4,6 +4,7 @@ #include "LuaCallNode.h" #include "StringDecoder.h" +#include "GameFramework/Actor.h" #include "BlueprintActionDatabaseRegistrar.h" #include "BlueprintNodeSpawner.h" #include "Containers/EnumAsByte.h" diff --git a/build.py b/build.py index 420e4810..f98b5aa9 100755 --- a/build.py +++ b/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' 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}") return mode @@ -400,6 +400,9 @@ os.chdir(f"{INTEGRATION}/EnginePatches") if MODE == "ccjson": build_intellisense_database_for_clangd() +if MODE == "code-workspace": + generate_integration_code_workspace() + if MODE == "all": unzip_unreal_engine_and_apply_patch() generate_buildconfiguration_xml()