Files
UnrealEngine/Engine/Source/Editor/UnrealEd/Private/Kismet2/BlueprintEditorUtils.cpp
2025-05-18 13:04:45 +08:00

10597 lines
361 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Kismet2/BlueprintEditorUtils.h"
#include "Kismet2/KismetReinstanceUtilities.h"
#include "Algo/AnyOf.h"
#include "Algo/Copy.h"
#include "Algo/RemoveIf.h"
#include "Algo/Transform.h"
#include "BlueprintCompilationManager.h"
#include "UObject/Interface.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Components/ActorComponent.h"
#include "Engine/Level.h"
#include "GameFramework/Actor.h"
#include "Engine/SimpleConstructionScript.h"
#include "Engine/SCS_Node.h"
#include "Stats/StatsMisc.h"
#include "Modules/ModuleManager.h"
#include "UObject/UObjectIterator.h"
#include "UObject/StructOnScope.h"
#include "UObject/MetaData.h"
#include "UObject/TextProperty.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Styling/CoreStyle.h"
#include "Layout/WidgetPath.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Styling/AppStyle.h"
#include "Exporters/Exporter.h"
#include "Animation/AnimInstance.h"
#include "Editor/EditorEngine.h"
#include "Editor/UnrealEdEngine.h"
#include "Animation/AnimBlueprint.h"
#include "Engine/MemberReference.h"
#include "ThumbnailRendering/ThumbnailManager.h"
#include "ThumbnailRendering/BlueprintThumbnailRenderer.h"
#include "Engine/LevelScriptActor.h"
#include "Components/TimelineComponent.h"
#include "Engine/TimelineTemplate.h"
#include "StructUtils/UserDefinedStruct.h"
#include "UObject/PropertyPortFlags.h"
#include "Serialization/ArchiveReplaceObjectRef.h"
#include "EngineUtils.h"
#include "EdMode.h"
#include "Dialogs/Dialogs.h"
#include "UnrealEdGlobals.h"
#include "Engine/LevelScriptBlueprint.h"
#include "UObject/BlueprintsObjectVersion.h"
#include "Kismet2/CompilerResultsLog.h"
#include "Algo/Transform.h"
#include "Algo/Count.h"
#include "KismetCompilerModule.h"
#include "EdGraphSchema_K2_Actions.h"
#include "K2Node_Event.h"
#include "K2Node_ActorBoundEvent.h"
#include "K2Node_CallFunction.h"
#include "K2Node_AddComponent.h"
#include "K2Node_BaseMCDelegate.h"
#include "K2Node_AddDelegate.h"
#include "K2Node_BaseAsyncTask.h"
#include "K2Node_Variable.h"
#include "K2Node_CallParentFunction.h"
#include "K2Node_ComponentBoundEvent.h"
#include "K2Node_Tunnel.h"
#include "K2Node_Composite.h"
#include "K2Node_CreateDelegate.h"
#include "K2Node_CustomEvent.h"
#include "K2Node_FunctionTerminator.h"
#include "K2Node_FunctionEntry.h"
#include "K2Node_FunctionResult.h"
#include "K2Node_GetClassDefaults.h"
#include "K2Node_Literal.h"
#include "K2Node_MacroInstance.h"
#include "K2Node_MathExpression.h"
#include "K2Node_SpawnActorFromClass.h"
#include "K2Node_StructOperation.h"
#include "K2Node_TemporaryVariable.h"
#include "K2Node_Timeline.h"
#include "K2Node_Knot.h"
#include "MaterialGraph/MaterialGraphNode_Composite.h"
#include "AnimGraphNode_StateMachineBase.h"
#include "AnimStateNodeBase.h"
#include "AnimStateNode.h"
#include "AnimStateTransitionNode.h"
#include "AnimationTransitionSchema.h"
#include "AnimationGraph.h"
#include "AnimationGraphSchema.h"
#include "AnimationStateMachineGraph.h"
#include "AnimationTransitionGraph.h"
#include "AnimStateConduitNode.h"
#include "AnimGraphNode_StateMachine.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Kismet2/KismetDebugUtilities.h"
#include "Kismet2/StructureEditorUtils.h"
#include "ScopedTransaction.h"
#include "ClassViewerFilter.h"
#include "NodeDependingOnEnumInterface.h"
#include "BlueprintEditorModule.h"
#include "BlueprintEditor.h"
#include "Kismet2/Kismet2NameValidators.h"
#include "Misc/DefaultValueHelper.h"
#include "ObjectEditorUtils.h"
#include "Toolkits/ToolkitManager.h"
#include "UnrealExporter.h"
#include "BlueprintActionDatabase.h"
#include "BlueprintEditorSettings.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "IBlutilityModule.h"
#include "Engine/InheritableComponentHandler.h"
#include "LevelEditor.h"
#include "EditorCategoryUtils.h"
#include "Styling/SlateIconFinder.h"
#include "BaseWidgetBlueprint.h"
#include "Components/Widget.h"
#include "UObject/UObjectThreadContext.h"
#include "AnimGraphNode_LinkedInputPose.h"
#include "AnimGraphNode_Root.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "Misc/FeedbackContext.h"
#include "Containers/ArrayView.h"
#include "UObject/FastReferenceCollector.h"
#include "Elements/Framework/TypedElementRegistry.h"
#include "Elements/Interfaces/TypedElementHierarchyInterface.h"
#include "Elements/Interfaces/TypedElementObjectInterface.h"
#include "Misc/MessageDialog.h"
#include "UObject/FastReferenceCollector.h"
#include "INotifyFieldValueChanged.h"
#include "Components/SkeletalMeshComponent.h"
#define LOCTEXT_NAMESPACE "Blueprint"
DEFINE_LOG_CATEGORY(LogBlueprintDebug);
DEFINE_STAT(EKismetCompilerStats_NotifyBlueprintChanged);
DECLARE_CYCLE_STAT(TEXT("Mark Blueprint as Structurally Modified"), EKismetCompilerStats_MarkBlueprintasStructurallyModified, STATGROUP_KismetCompiler);
DECLARE_CYCLE_STAT(TEXT("Refresh External DependencyNodes"), EKismetCompilerStats_RefreshExternalDependencyNodes, STATGROUP_KismetCompiler);
static void SortNodes(TArray<UK2Node*>& AllNodes, bool bSortByPriorityOnly = false)
{
auto SortNodesInternalByPriorityOnly = [](const UK2Node& A, const UK2Node& B)
{
return A.GetNodeRefreshPriority() > B.GetNodeRefreshPriority();
};
auto SortNodesInternal = [SortNodesInternalByPriorityOnly](const UK2Node& A, const UK2Node& B)
{
const bool NodeAChangesStructure = A.NodeCausesStructuralBlueprintChange();
const bool NodeBChangesStructure = B.NodeCausesStructuralBlueprintChange();
if (NodeAChangesStructure != NodeBChangesStructure)
{
return NodeAChangesStructure;
}
return SortNodesInternalByPriorityOnly(A, B);
};
if (AllNodes.Num() > 1)
{
if (bSortByPriorityOnly)
{
AllNodes.Sort(SortNodesInternalByPriorityOnly);
}
else
{
AllNodes.Sort(SortNodesInternal);
}
}
}
/**
* This helper does a depth first search, looking for the highest parent class that
* implements the specified interface.
*
* @param Class The class whose inheritance tree you want to check (NOTE: this class is checked itself as well).
* @param Interface The interface that you're looking for.
* @return NULL if the interface wasn't found, otherwise the highest parent class that implements the interface.
*/
static UClass* FindInheritedInterface(UClass* const Class, FBPInterfaceDescription const& Interface)
{
UClass* ClassWithInterface = nullptr;
if (Class != nullptr)
{
UClass* const ParentClass = Class->GetSuperClass();
// search depth first so that we may find the highest parent in the chain that implements this interface
ClassWithInterface = FindInheritedInterface(ParentClass, Interface);
if (ClassWithInterface == nullptr)
{
for (const FImplementedInterface& ImplementedInterface : Class->Interfaces)
{
if (ImplementedInterface.Class == Interface.Interface)
{
ClassWithInterface = Class;
break;
}
}
}
}
return ClassWithInterface;
}
/**
* This helper can be used to find a duplicate interface that is implemented higher
* up the inheritance hierarchy (which can happen when you change parents or add an
* interface to a parent that's already implemented by a child).
*
* @param Interface The interface you wish to find a duplicate of.
* @param Blueprint The blueprint you wish to search.
* @return True if one of the blueprint's super classes implements the specified interface, false if the child is free to implement it.
*/
static bool IsInterfaceImplementedByParent(FBPInterfaceDescription const& Interface, UBlueprint const* const Blueprint)
{
check(Blueprint != nullptr);
return (FindInheritedInterface(Blueprint->ParentClass, Interface) != nullptr);
}
/**
* A helper function that takes two nodes belonging to the same graph and deletes
* one, replacing it with the other (moving over pin connections, etc.).
*
* @param OldNode The node you want deleted.
* @param NewNode The new replacement node that should take OldNode's place.
*/
static void ReplaceNode(UK2Node* OldNode, UK2Node* NewNode)
{
check(OldNode->GetClass() == NewNode->GetClass());
check(OldNode->GetOuter() == NewNode->GetOuter());
UEdGraphSchema_K2 const* K2Schema = GetDefault<UEdGraphSchema_K2>();
K2Schema->BreakNodeLinks(*NewNode);
for (UEdGraphPin* OldPin : OldNode->Pins)
{
UEdGraphPin* NewPin = NewNode->FindPinChecked(OldPin->PinName);
NewPin->MovePersistentDataFromOldPin(*OldPin);
}
NewNode->NodePosX = OldNode->NodePosX;
NewNode->NodePosY = OldNode->NodePosY;
FBlueprintEditorUtils::RemoveNode(OldNode->GetBlueprint(), OldNode, /* bDontRecompile =*/ true);
}
/**
* Promotes any graphs that belong to the specified interface, and repurposes them
* as parent overrides (function graphs that implement a parent's interface).
*
* @param Interface The interface you're looking to promote.
* @param BlueprintObj The blueprint that you want this applied to.
*/
static void PromoteInterfaceImplementationToOverride(FBPInterfaceDescription const& Interface, UBlueprint* const BlueprintObj)
{
check(BlueprintObj != nullptr);
// find the parent whose interface we're overriding
UClass* ParentClass = FindInheritedInterface(BlueprintObj->ParentClass, Interface);
if (ParentClass != nullptr)
{
for (UEdGraph* InterfaceGraph : Interface.Graphs)
{
check(InterfaceGraph != nullptr);
// The graph can be deleted now that it is a simple function override
InterfaceGraph->bAllowDeletion = true;
// Interface functions are ready to be a function graph outside the box,
// there will be no auto-call to parent though to maintain current functionality
// in the graph
BlueprintObj->FunctionGraphs.Add(InterfaceGraph);
// No validation should be necessary here. Child blueprints will have interfaces conformed during their own compilation.
}
// if any graphs were moved
if (Interface.Graphs.Num() > 0)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BlueprintObj);
}
}
}
/**
* Looks through the specified graph for any references to the specified
* variable, and renames them accordingly.
*
* @param InBlueprint The blueprint that you want to search through.
* @param InVariableClass The class that owns the variable that we're renaming
* @param InGraph Graph to scope the rename to
* @param InOldVarName The current name of the variable we want to replace
* @param InNewVarName The name that we wish to change all references to
*/
static bool RenameVariableReferencesInGraph(UBlueprint* InBlueprint, UClass* InVariableClass, UEdGraph* InGraph, const FName& InOldVarName, const FName& InNewVarName)
{
bool bFoundReference = false;
for(UEdGraphNode* GraphNode : InGraph->Nodes)
{
// Allow node to handle variable renaming
if (UK2Node* const K2Node = Cast<UK2Node>(GraphNode))
{
bFoundReference |= K2Node->ReferencesVariable(InOldVarName, nullptr);
K2Node->HandleVariableRenamed(InBlueprint, InVariableClass, InGraph, InOldVarName, InNewVarName);
}
}
return bFoundReference;
}
/**
* Looks through the specified graph for any references to the specified
* function, and renames them accordingly.
*
* @param InBlueprint The blueprint that you want to search through.
* @param InFunctionClass The class that owns the function that we're renaming
* @param InGraph Graph to scope the rename to
* @param InOldFuncName The current name of the function we want to replace
* @param InNewFuncName The name that we wish to change all references to
*/
static bool RenameFunctionReferencesInGraph(UBlueprint* InBlueprint, UClass* InFunctionClass, UEdGraph* InGraph, const FName& InOldFuncName, const FName& InNewFuncName)
{
bool bFoundReference = false;
for(UEdGraphNode* GraphNode : InGraph->Nodes)
{
// Allow node to handle function renaming
if (UK2Node* const K2Node = Cast<UK2Node>(GraphNode))
{
bFoundReference |= K2Node->ReferencesFunction(InOldFuncName, nullptr);
K2Node->HandleFunctionRenamed(InBlueprint, InFunctionClass, InGraph, InOldFuncName, InNewFuncName);
}
}
return bFoundReference;
}
/**
* Gathers all variable nodes from all graph's subgraph nodes
*
* @param Graph The Graph to search
* @param OutVariableNodes variable node array to write to
*/
static void GetAllChildGraphVariables(UEdGraph* Graph, TArray<UK2Node_Variable*>& OutVariableNodes)
{
for (UEdGraph* SubGraph : Graph->SubGraphs)
{
check(SubGraph != nullptr);
SubGraph->GetNodesOfClass<UK2Node_Variable>(OutVariableNodes);
if (!SubGraph->SubGraphs.IsEmpty())
{
GetAllChildGraphVariables(SubGraph, OutVariableNodes);
}
}
}
FBlueprintEditorUtils::FOnRenameVariableReferences FBlueprintEditorUtils::OnRenameVariableReferencesEvent;
void FBlueprintEditorUtils::RenameVariableReferences(UBlueprint* Blueprint, UClass* VariableClass, const FName& OldVarName, const FName& NewVarName)
{
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
// Update any graph nodes that reference the old variable name to instead reference the new name
for(UEdGraph* CurrentGraph : AllGraphs)
{
if (RenameVariableReferencesInGraph(Blueprint, VariableClass, CurrentGraph, OldVarName, NewVarName))
{
MarkBlueprintAsModified(Blueprint);
}
}
OnRenameVariableReferencesEvent.Broadcast(Blueprint, VariableClass, OldVarName, NewVarName);
}
FBlueprintEditorUtils::FOnRenameFunctionReferences FBlueprintEditorUtils::OnRenameFunctionReferencesEvent;
void FBlueprintEditorUtils::RenameFunctionReferences(UBlueprint* Blueprint, UClass* FunctionClass, const FName& OldFuncName, const FName& NewFuncName)
{
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
// Update any graph nodes that reference the old function name to instead reference the new name
for(UEdGraph* CurrentGraph : AllGraphs)
{
if (RenameFunctionReferencesInGraph(Blueprint, FunctionClass, CurrentGraph, OldFuncName, NewFuncName))
{
MarkBlueprintAsModified(Blueprint);
}
}
OnRenameFunctionReferencesEvent.Broadcast(Blueprint, FunctionClass, OldFuncName, NewFuncName);
}
//////////////////////////////////////
// FBasePinChangeHelper
void FBasePinChangeHelper::Broadcast(UBlueprint* InBlueprint, UK2Node_EditablePinBase* InTargetNode, UEdGraph* Graph)
{
UK2Node_Tunnel* TunnelNode = Cast<UK2Node_Tunnel>(InTargetNode);
const UK2Node_FunctionTerminator* FunctionDefNode = Cast<const UK2Node_FunctionTerminator>(InTargetNode);
const UK2Node_Event* EventNode = Cast<const UK2Node_Event>(InTargetNode);
if (TunnelNode)
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph);
const bool bIsTopLevelFunctionGraph = Blueprint->MacroGraphs.Contains(Graph);
if (bIsTopLevelFunctionGraph)
{
// Editing a macro, hit all loaded instances (in open blueprints)
for (TObjectIterator<UK2Node_MacroInstance> It(RF_Transient); It; ++It)
{
UK2Node_MacroInstance* MacroInstance = *It;
if (NodeIsNotTransient(MacroInstance) && (MacroInstance->GetMacroGraph() == Graph))
{
EditMacroInstance(MacroInstance, FBlueprintEditorUtils::FindBlueprintForNode(MacroInstance));
}
}
}
else if(NodeIsNotTransient(TunnelNode))
{
// Editing a composite node, hit the single instance in the parent graph
EditCompositeTunnelNode(TunnelNode);
}
}
else if (FunctionDefNode || EventNode)
{
UFunction* Func = FFunctionFromNodeHelper::FunctionFromNode(FunctionDefNode ? static_cast<const UK2Node*>(FunctionDefNode) : static_cast<const UK2Node*>(EventNode));
const FName FuncName = Func
? Func->GetFName()
: (FunctionDefNode ? FunctionDefNode->FunctionReference.GetMemberName() : EventNode->GetFunctionName());
const UClass* SignatureClass = Func
? Func->GetOwnerClass()
: (UClass*)(FunctionDefNode ? FunctionDefNode->FunctionReference.GetMemberParentClass() : nullptr);
const bool bIsInterface = FBlueprintEditorUtils::IsInterfaceBlueprint(InBlueprint);
// Reconstruct all function call sites that call this function (in open blueprints)
for (TObjectIterator<UK2Node_CallFunction> It(RF_Transient); It; ++It)
{
UK2Node_CallFunction* CallSite = *It;
if (NodeIsNotTransient(CallSite))
{
UBlueprint* CallSiteBlueprint = FBlueprintEditorUtils::FindBlueprintForNode(CallSite);
if (!CallSiteBlueprint)
{
// the node doesn't have a Blueprint in its outer chain,
// probably signifying that it is part of a graph that has
// been removed by the user (and moved off the Blueprint)
continue;
}
const bool bValidSchema = CallSite->GetSchema() != nullptr;
const bool bNameMatches = (CallSite->FunctionReference.GetMemberName() == FuncName);
if (bNameMatches && bValidSchema)
{
if (bIsInterface)
{
if (FBlueprintEditorUtils::FindFunctionInImplementedInterfaces(CallSiteBlueprint, FuncName))
{
EditCallSite(CallSite, CallSiteBlueprint);
}
}
else
{
const UClass* MemberParentClass = CallSite->FunctionReference.GetMemberParentClass(CallSite->GetBlueprintClassFromNode());
const bool bClassMatchesEasy = (MemberParentClass != nullptr)
&& ((SignatureClass != nullptr && MemberParentClass->IsChildOf(SignatureClass)) || MemberParentClass->IsChildOf(InBlueprint->GeneratedClass));
const bool bClassMatchesHard = !bClassMatchesEasy && CallSite->FunctionReference.IsSelfContext() && (SignatureClass == nullptr)
&& (CallSiteBlueprint == InBlueprint || (CallSiteBlueprint->SkeletonGeneratedClass && CallSiteBlueprint->SkeletonGeneratedClass->IsChildOf(InBlueprint->SkeletonGeneratedClass)));
if (bClassMatchesEasy || bClassMatchesHard)
{
EditCallSite(CallSite, CallSiteBlueprint);
}
}
}
}
}
if(FBlueprintEditorUtils::IsDelegateSignatureGraph(Graph))
{
FName GraphName = Graph->GetFName();
for (TObjectIterator<UK2Node_BaseMCDelegate> It(RF_Transient); It; ++It)
{
if(NodeIsNotTransient(*It) && (GraphName == It->GetPropertyName()))
{
UBlueprint* CallSiteBlueprint = FBlueprintEditorUtils::FindBlueprintForNode(*It);
EditDelegates(*It, CallSiteBlueprint);
}
}
}
for (TObjectIterator<UK2Node_CreateDelegate> It(RF_Transient); It; ++It)
{
if(NodeIsNotTransient(*It))
{
EditCreateDelegates(*It);
}
}
}
}
//////////////////////////////////////
// FParamsChangedHelper
void FParamsChangedHelper::EditCompositeTunnelNode(UK2Node_Tunnel* TunnelNode)
{
if (TunnelNode->InputSinkNode != nullptr)
{
TunnelNode->InputSinkNode->ReconstructNode();
}
if (TunnelNode->OutputSourceNode != nullptr)
{
TunnelNode->OutputSourceNode->ReconstructNode();
}
}
void FParamsChangedHelper::EditMacroInstance(UK2Node_MacroInstance* MacroInstance, UBlueprint* Blueprint)
{
MacroInstance->ReconstructNode();
if (Blueprint)
{
ModifiedBlueprints.Add(Blueprint);
}
}
void FParamsChangedHelper::EditCallSite(UK2Node_CallFunction* CallSite, UBlueprint* Blueprint)
{
CallSite->Modify();
CallSite->ReconstructNode();
if (Blueprint != nullptr)
{
ModifiedBlueprints.Add(Blueprint);
}
}
void FParamsChangedHelper::EditDelegates(UK2Node_BaseMCDelegate* CallSite, UBlueprint* Blueprint)
{
CallSite->Modify();
CallSite->ReconstructNode();
if (UK2Node_AddDelegate* AssignNode = Cast<UK2Node_AddDelegate>(CallSite))
{
if (UEdGraphPin* DelegateInPin = AssignNode->GetDelegatePin())
{
for (UEdGraphPin* DelegateOutPin : DelegateInPin->LinkedTo)
{
if (UK2Node_CustomEvent* CustomEventNode = (DelegateOutPin ? Cast<UK2Node_CustomEvent>(DelegateOutPin->GetOwningNode()) : nullptr))
{
CustomEventNode->ReconstructNode();
}
}
}
}
if (Blueprint != nullptr)
{
ModifiedBlueprints.Add(Blueprint);
}
}
void FParamsChangedHelper::EditCreateDelegates(UK2Node_CreateDelegate* CallSite)
{
UBlueprint* Blueprint = nullptr;
UEdGraph* Graph = nullptr;
CallSite->HandleAnyChange(Graph, Blueprint);
if(Blueprint)
{
ModifiedBlueprints.Add(Blueprint);
}
if(Graph)
{
ModifiedGraphs.Add(Graph);
}
}
//////////////////////////////////////
// FUCSComponentId
FUCSComponentId::FUCSComponentId(const UK2Node_AddComponent* UCSNode)
: GraphNodeGuid(UCSNode->NodeGuid)
{
}
//////////////////////////////////////
// FBlueprintEditorUtils
FBlueprintEditorUtils::FOnRefreshAllNodes FBlueprintEditorUtils::OnRefreshAllNodesEvent;
namespace UE::Private
{
bool UNREALED_API GraphContainsSetSkeletalMeshAssetNode(UEdGraph* Graph)
{
struct FNodeGraphTraverser
{
TSet<UEdGraphNode*> VisitedNodes;
bool TraverseGraph(UEdGraph* Graph)
{
bool bFoundSetSkeletalMeshCall = false;
for (int32 Index = 0; Index < Graph->Nodes.Num(); Index++)
{
UK2Node_CallFunction* FunctionNode = Cast<UK2Node_CallFunction>(Graph->Nodes[Index]);
if (FunctionNode && !VisitedNodes.Contains(FunctionNode))
{
VisitedNodes.Add(FunctionNode);
const UEdGraphNode* ResultEventNode = nullptr;
UEdGraph* FunctionGraph = FunctionNode->GetFunctionGraph(ResultEventNode);
if (FunctionGraph)
{
bFoundSetSkeletalMeshCall = TraverseGraph(FunctionGraph);
if (bFoundSetSkeletalMeshCall)
{
break;
}
}
else if (FunctionNode->FunctionReference.GetMemberName() == GET_FUNCTION_NAME_CHECKED(USkeletalMeshComponent, SetSkeletalMeshAsset))
{
bFoundSetSkeletalMeshCall = true;
break;
}
}
else
{
UK2Node_MacroInstance* MacroInstanceNode = Cast<UK2Node_MacroInstance>(Graph->Nodes[Index]);
if (MacroInstanceNode && !VisitedNodes.Contains(MacroInstanceNode))
{
VisitedNodes.Add(MacroInstanceNode);
UEdGraph* MacroGraph = MacroInstanceNode->GetMacroGraph();
if (MacroGraph)
{
bFoundSetSkeletalMeshCall = TraverseGraph(MacroGraph);
if (bFoundSetSkeletalMeshCall)
{
break;
}
}
}
}
}
return bFoundSetSkeletalMeshCall;
}
};
FNodeGraphTraverser GraphTraverser;
return GraphTraverser.TraverseGraph(Graph);
}
}
void FBlueprintEditorUtils::RefreshAllNodes(UBlueprint* Blueprint)
{
if (!Blueprint || (Blueprint->HasAllFlags(RF_WasLoaded) && !Blueprint->HasAllFlags(RF_LoadCompleted)))
{
UE_LOG(LogBlueprint, Warning,
TEXT("RefreshAllNodes was called on an invalid or incompletely loaded blueprint '%s'"),
Blueprint ? *Blueprint->GetFullName() : TEXT("NULL"));
return;
}
TArray<UK2Node*> AllNodes;
FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, AllNodes);
const bool bIsMacro = (Blueprint->BlueprintType == BPTYPE_MacroLibrary);
SortNodes(AllNodes);
bool bLastChangesStructure = (AllNodes.Num() > 0) ? AllNodes[0]->NodeCausesStructuralBlueprintChange() : true;
for( TArray<UK2Node*>::TIterator NodeIt(AllNodes); NodeIt; ++NodeIt )
{
UK2Node* CurrentNode = *NodeIt;
// See if we've finished the batch of nodes that affect structure, and recompile the skeleton if needed
const bool bCurrentChangesStructure = CurrentNode->NodeCausesStructuralBlueprintChange();
if( bLastChangesStructure != bCurrentChangesStructure )
{
// Make sure sorting was valid!
check(bLastChangesStructure && !bCurrentChangesStructure);
// Recompile the skeleton class, now that all changes to entry point structure has taken place
// Ignore this for macros
if (!bIsMacro)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
bLastChangesStructure = bCurrentChangesStructure;
}
//@todo: Do we really need per-schema refreshing?
const UEdGraphSchema* Schema = CurrentNode->GetGraph()->GetSchema();
Schema->ReconstructNode(*CurrentNode, true);
}
// If all nodes change structure, catch that case and recompile now
if( bLastChangesStructure )
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
OnRefreshAllNodesEvent.Broadcast(Blueprint);
}
FBlueprintEditorUtils::FOnReconstructAllNodes FBlueprintEditorUtils::OnReconstructAllNodesEvent;
void FBlueprintEditorUtils::ReconstructAllNodes(UBlueprint* Blueprint)
{
if (!Blueprint || (Blueprint->HasAllFlags(RF_WasLoaded) && !Blueprint->HasAllFlags(RF_LoadCompleted)))
{
UE_LOG(LogBlueprint, Warning,
TEXT("ReconstructAllNodes was called on an invalid or incompletely loaded blueprint '%s'"),
Blueprint ? *Blueprint->GetFullName() : TEXT("NULL"));
return;
}
TArray<UK2Node*> AllNodes;
FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, AllNodes);
SortNodes(AllNodes, true);
for (TArray<UK2Node*>::TIterator NodeIt(AllNodes); NodeIt; ++NodeIt)
{
UK2Node* CurrentNode = *NodeIt;
//@todo: Do we really need per-schema refreshing?
const UEdGraphSchema* Schema = CurrentNode->GetGraph()->GetSchema();
Schema->ReconstructNode(*CurrentNode, true);
}
OnReconstructAllNodesEvent.Broadcast(Blueprint);
}
void FBlueprintEditorUtils::ReplaceDeprecatedNodes(UBlueprint* Blueprint)
{
Blueprint->ReplaceDeprecatedNodes();
}
void FBlueprintEditorUtils::RefreshExternalBlueprintDependencyNodes(UBlueprint* Blueprint, UStruct* RefreshOnlyChild)
{
BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_RefreshExternalDependencyNodes);
if (!Blueprint || (Blueprint->HasAllFlags(RF_WasLoaded) && !Blueprint->HasAllFlags(RF_LoadCompleted)))
{
UE_LOG(LogBlueprint, Warning,
TEXT("RefreshExternalBlueprintDependencyNodes was called on an invalid or incompletely loaded blueprint '%s'"),
Blueprint ? *Blueprint->GetFullName() : TEXT("NULL"));
return;
}
TArray<UK2Node*> AllNodes;
FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, AllNodes);
if (!RefreshOnlyChild)
{
for (UK2Node* Node : AllNodes)
{
if (Node->HasExternalDependencies())
{
//@todo: Do we really need per-schema refreshing?
const UEdGraphSchema* Schema = Node->GetGraph()->GetSchema();
Schema->ReconstructNode(*Node, true);
}
}
}
else
{
for (UK2Node* Node : AllNodes)
{
TArray<UStruct*> Dependencies;
if (Node->HasExternalDependencies(&Dependencies))
{
for (UStruct* Struct : Dependencies)
{
bool bShouldRefresh = Struct->IsChildOf(RefreshOnlyChild);
if (!bShouldRefresh)
{
UClass* OwnerClass = Struct->GetOwnerClass();
if (ensureMsgf(!OwnerClass || !OwnerClass->GetClass()->IsChildOf<UBlueprintGeneratedClass>() || OwnerClass->ClassGeneratedBy
, TEXT("Malformed Blueprint class (%s) - bad node dependency, unable to determine if the %s node (%s) should be refreshed or not. Currently compiling: %s")
, *OwnerClass->GetName()
, *Node->GetClass()->GetName()
, *Node->GetPathName()
, *Blueprint->GetName()) )
{
bShouldRefresh |= OwnerClass &&
(OwnerClass->IsChildOf(RefreshOnlyChild) || OwnerClass->GetAuthoritativeClass()->IsChildOf(RefreshOnlyChild));
if (!bShouldRefresh && OwnerClass && Struct->IsA<UFunction>() && OwnerClass->HasAnyClassFlags(CLASS_Interface))
{
if (UClass* RefreshClass = Cast<UClass>(RefreshOnlyChild))
{
bShouldRefresh = RefreshClass->ImplementsInterface(OwnerClass);
}
}
}
}
if (bShouldRefresh)
{
//@todo: Do we really need per-schema refreshing?
const UEdGraphSchema* Schema = Node->GetGraph()->GetSchema();
Schema->ReconstructNode(*Node, true);
break;
}
}
}
}
}
}
void FBlueprintEditorUtils::RefreshGraphNodes(const UEdGraph* Graph)
{
TArray<UK2Node*> AllNodes;
Graph->GetNodesOfClass(AllNodes);
for (UK2Node* Node : AllNodes)
{
const UEdGraphSchema* Schema = Node->GetGraph()->GetSchema();
Schema->ReconstructNode(*Node, true);
}
}
void FBlueprintEditorUtils::PreloadMembers(UObject* InObject)
{
// Collect a list of all things this element owns
TArray<UObject*> BPMemberReferences;
FReferenceFinder ComponentCollector(BPMemberReferences, InObject, false, true, true, true);
ComponentCollector.FindReferences(InObject);
// Iterate over the list, and preload everything so it is valid for refreshing
for( TArray<UObject*>::TIterator it(BPMemberReferences); it; ++it )
{
UObject* CurrentObject = *it;
if( CurrentObject->HasAnyFlags(RF_NeedLoad) )
{
FLinkerLoad* Linker = CurrentObject->GetLinker();
if (Linker)
{
Linker->Preload(CurrentObject);
}
PreloadMembers(CurrentObject);
}
}
}
void FBlueprintEditorUtils::PreloadConstructionScript(UBlueprint* Blueprint)
{
if (Blueprint)
{
PreloadConstructionScript(Blueprint->SimpleConstructionScript);
}
}
void FBlueprintEditorUtils::PreloadConstructionScript(USimpleConstructionScript* SimpleConstructionScript)
{
if (!SimpleConstructionScript)
{
return;
}
if (FLinkerLoad* TargetLinker = SimpleConstructionScript->GetLinker())
{
TargetLinker->Preload(SimpleConstructionScript);
if (USCS_Node* DefaultSceneRootNode = SimpleConstructionScript->GetDefaultSceneRootNode())
{
DefaultSceneRootNode->PreloadChain();
}
const TArray<USCS_Node*>& RootNodes = SimpleConstructionScript->GetRootNodes();
for (int32 NodeIndex = 0; NodeIndex < RootNodes.Num(); ++NodeIndex)
{
RootNodes[NodeIndex]->PreloadChain();
}
}
for (USCS_Node* SCSNode : SimpleConstructionScript->GetAllNodes())
{
if (SCSNode)
{
SCSNode->ValidateGuid();
}
}
}
void FBlueprintEditorUtils::PatchNewCDOIntoLinker(UObject* CDO, FLinkerLoad* Linker, int32 ExportIndex, FUObjectSerializeContext* InLoadContext)
{
if (CDO != nullptr && Linker != nullptr)
{
Linker->PRIVATE_PatchNewObjectIntoExport(ExportIndex, CDO, InLoadContext);
CDO->CheckDefaultSubobjects();
}
}
UClass* FBlueprintEditorUtils::FindFirstNativeClass(UClass* Class)
{
for(; Class; Class = Class->GetSuperClass() )
{
if( 0 != (Class->ClassFlags & CLASS_Native))
{
break;
}
}
return Class;
}
const UClass* FBlueprintEditorUtils::FindFirstNativeClass(const UClass* Class)
{
return FindFirstNativeClass(const_cast<UClass*>(Class));
}
bool FBlueprintEditorUtils::IsNativeSignature(const UFunction* Fn)
{
if(UClass* OwningClass = Fn->GetOwnerClass())
{
if(UClass* PotentialNativeOwner = FindFirstNativeClass(OwningClass))
{
return PotentialNativeOwner->FindFunctionByName(Fn->GetFName()) != nullptr;
}
}
// All UFunctions are owned by UClasses, but if we encounter one that
// is not lets just fall back to the flag:
return Fn->HasAnyFunctionFlags(FUNC_Native);
}
void FBlueprintEditorUtils::GetAllGraphNames(const UBlueprint* Blueprint, TSet<FName>& GraphNames)
{
TArray< UEdGraph* > GraphList;
Blueprint->GetAllGraphs(GraphList);
for(int32 GraphIdx = 0; GraphIdx < GraphList.Num(); ++GraphIdx)
{
GraphNames.Add(GraphList[GraphIdx]->GetFName());
}
// Include all functions from parents because they should never conflict
TArray<UBlueprint*> ParentBPStack;
UBlueprint::GetBlueprintHierarchyFromClass(Blueprint->SkeletonGeneratedClass, ParentBPStack);
for (int32 StackIndex = ParentBPStack.Num() - 1; StackIndex >= 0; --StackIndex)
{
UBlueprint* ParentBP = ParentBPStack[StackIndex];
check(ParentBP != nullptr);
for(int32 FunctionIndex = 0; FunctionIndex < ParentBP->FunctionGraphs.Num(); ++FunctionIndex)
{
GraphNames.Add(ParentBP->FunctionGraphs[FunctionIndex]->GetFName());
}
}
}
void FBlueprintEditorUtils::GetCompilerRelevantNodeLinks(UEdGraphPin* FromPin, FCompilerRelevantNodeLinkArray& OutNodeLinks)
{
if(FromPin)
{
// Start with the given pin's owning node
UK2Node* OwningNode = Cast<UK2Node>(FromPin->GetOwningNode());
if(OwningNode)
{
// If this node is not compiler relevant
if(!OwningNode->IsCompilerRelevant())
{
// And if this node has a matching "pass-through" pin
FromPin = OwningNode->GetPassThroughPin(FromPin);
if(FromPin)
{
// Recursively check each link for a compiler-relevant node that will "pass through" this node at compile time
for (UEdGraphPin* LinkedPin : FromPin->LinkedTo)
{
GetCompilerRelevantNodeLinks(LinkedPin, OutNodeLinks);
}
}
}
else
{
OutNodeLinks.Add(FCompilerRelevantNodeLink(OwningNode, FromPin));
}
}
}
}
UK2Node* FBlueprintEditorUtils::FindFirstCompilerRelevantNode(UEdGraphPin* FromPin)
{
FCompilerRelevantNodeLinkArray RelevantNodeLinks;
GetCompilerRelevantNodeLinks(FromPin, RelevantNodeLinks);
return RelevantNodeLinks.Num() > 0 ? RelevantNodeLinks[0].Node : nullptr;
}
UEdGraphPin* FBlueprintEditorUtils::FindFirstCompilerRelevantLinkedPin(UEdGraphPin* FromPin)
{
FCompilerRelevantNodeLinkArray RelevantNodeLinks;
GetCompilerRelevantNodeLinks(FromPin, RelevantNodeLinks);
return RelevantNodeLinks.Num() > 0 ? RelevantNodeLinks[0].LinkedPin : nullptr;
}
void FBlueprintEditorUtils::RemoveAllLocalBookmarks(const UBlueprint* ForBlueprint)
{
if (ForBlueprint)
{
bool bSaveConfig = false;
const FString BPPackageName = ForBlueprint->GetOutermost()->GetName();
UBlueprintEditorSettings* LocalSettings = GetMutableDefault<UBlueprintEditorSettings>();
for (int32 i = 0; i < LocalSettings->BookmarkNodes.Num(); ++i)
{
const FBPEditorBookmarkNode& BookmarkNode = LocalSettings->BookmarkNodes[i];
const FEditedDocumentInfo* BookmarkInfo = LocalSettings->Bookmarks.Find(BookmarkNode.NodeGuid);
if (BookmarkInfo != nullptr && BookmarkInfo->EditedObjectPath.GetLongPackageName() == BPPackageName)
{
bSaveConfig = true;
LocalSettings->BookmarkNodes.RemoveAt(i--);
LocalSettings->Bookmarks.Remove(BookmarkNode.NodeGuid);
}
}
if (bSaveConfig)
{
LocalSettings->SaveConfig();
}
}
}
//////////////////////////////////////////////////////////////////////////
struct FRegenerationHelper
{
static void PreloadAndLinkIfNecessary(UStruct* Struct)
{
bool bChanged = false;
if (Struct->HasAnyFlags(RF_NeedLoad))
{
if (FLinkerLoad* Linker = Struct->GetLinker())
{
Linker->Preload(Struct);
bChanged = true;
}
}
const int32 OldPropertiesSize = Struct->GetPropertiesSize();
for (UField* Field = Struct->Children; Field; Field = Field->Next)
{
bChanged |= UBlueprint::ForceLoad(Field);
}
if (bChanged)
{
Struct->StaticLink(true);
ensure(Struct->IsA<UFunction>() || (OldPropertiesSize == Struct->GetPropertiesSize()) || !Struct->HasAnyFlags(RF_LoadCompleted));
// UStruct::Link is going to attempt to set the StructFlags, but it has no knowledge of UserDefinedStruct::DefaultStructInstance.
// We don't want to set CPF_ZeroConstructor if UserDefinedStruct::DefaultStructInstance has non zero data, so this call
// gives UUserDefinedStruct a chance to enforce its invariants. The cooked/EDL path relies on setting struct flags in
// serialize, after link has been called.
if(UUserDefinedStruct* AsUDS = Cast<UUserDefinedStruct>(Struct))
{
AsUDS->UpdateStructFlags();
}
}
}
static UBlueprint* GetGeneratingBlueprint(const UObject* Obj)
{
const UBlueprintGeneratedClass* BPGC = nullptr;
while (!BPGC && Obj)
{
BPGC = Cast<const UBlueprintGeneratedClass>(Obj);
Obj = Obj->GetOuter();
}
return UBlueprint::GetBlueprintFromClass(BPGC);
}
static void ProcessHierarchy(UStruct* Struct, TSet<UStruct*>& Dependencies)
{
if (Struct)
{
bool bAlreadyProcessed = false;
Dependencies.Add(Struct, &bAlreadyProcessed);
if (!bAlreadyProcessed)
{
ProcessHierarchy(Struct->GetSuperStruct(), Dependencies);
const UBlueprint* BP = GetGeneratingBlueprint(Struct);
const bool bProcessBPGClass = BP && !BP->bHasBeenRegenerated;
const bool bProcessUserDefinedStruct = Struct->IsA<UUserDefinedStruct>();
if (bProcessBPGClass || bProcessUserDefinedStruct)
{
PreloadAndLinkIfNecessary(Struct);
}
}
}
}
static void PreloadMacroSources(TSet<UBlueprint*>& MacroSources)
{
for (UBlueprint* BP : MacroSources)
{
if (!BP->bHasBeenRegenerated)
{
if (BP->HasAnyFlags(RF_NeedLoad))
{
if (FLinkerLoad* Linker = BP->GetLinker())
{
Linker->Preload(BP);
}
}
// at the point of blueprint regeneration (on load), we are guaranteed that blueprint dependencies (like this macro) have
// fully formed classes (meaning the blueprint class and all its direct dependencies have been loaded)... however, we do not
// get the guarantee that all of that blueprint's graph dependencies are loaded (hence, why we have to force load
// everything here); in the case of cyclic dependencies, macro dependencies could already be loaded, but in the midst of
// resolving thier own dependency placeholders (why a ForceLoad() call is not enough); this ensures that
// placeholder objects are properly resolved on nodes that will be injected by macro expansion
FLinkerLoad::PRIVATE_ForceLoadAllDependencies(BP->GetOutermost());
UBlueprint::ForceLoadMembers(BP);
}
}
}
/**
* A helper function that loads (and regenerates) interface dependencies.
* Accounts for circular dependencies by following how we handle parent
* classes in FLinkerLoad::RegenerateBlueprintClass() (that is, to complete
* the interface's compilation/regeneration before we utilize it for the
* specified blueprint).
*
* @param Blueprint The blueprint whose implemented interfaces you want loaded.
*/
static void PreloadInterfaces(UBlueprint* Blueprint)
{
#if WITH_EDITORONLY_DATA // ImplementedInterfaces is wrapped WITH_EDITORONLY_DATA
for (FBPInterfaceDescription const& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
UClass* InterfaceClass = InterfaceDesc.Interface;
UBlueprint* InterfaceBlueprint = InterfaceClass ? Cast<UBlueprint>(InterfaceClass->ClassGeneratedBy) : nullptr;
if (InterfaceBlueprint)
{
UBlueprint::ForceLoadMembers(InterfaceBlueprint);
if (InterfaceBlueprint->HasAnyFlags(RF_BeingRegenerated))
{
InterfaceBlueprint->RegenerateClass(InterfaceClass, InterfaceClass->GetDefaultObject(false));
}
}
}
#endif // #if WITH_EDITORONLY_DATA
}
static void LinkExternalDependencies(UBlueprint* Blueprint)
{
check(Blueprint);
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
TSet<UStruct*> Dependencies;
ProcessHierarchy(Blueprint->ParentClass, Dependencies);
for (const FBPVariableDescription& NewVar : Blueprint->NewVariables)
{
if (UObject* TypeObject = NewVar.VarType.PinSubCategoryObject.Get())
{
FLinkerLoad* Linker = TypeObject->GetLinker();
if (Linker && TypeObject->HasAnyFlags(RF_NeedLoad))
{
Linker->Preload(TypeObject);
}
}
if (UClass* TypeClass = NewVar.VarType.PinSubCategoryMemberReference.GetMemberParentClass())
{
FLinkerLoad* Linker = TypeClass->GetLinker();
if (Linker && TypeClass->HasAnyFlags(RF_NeedLoad))
{
Linker->Preload(TypeClass);
}
}
}
TSet<UBlueprint*> MacroSources;
TArray<UEdGraph*> Graphs;
Blueprint->GetAllGraphs(Graphs);
for (UEdGraph* Graph : Graphs)
{
if (Graph && !FBlueprintEditorUtils::IsGraphIntermediate(Graph))
{
const bool bIsDelegateSignatureGraph = FBlueprintEditorUtils::IsDelegateSignatureGraph(Graph);
TArray<UK2Node*> Nodes;
Graph->GetNodesOfClass(Nodes);
for (UK2Node* Node : Nodes)
{
if (Node)
{
TArray<UStruct*> LocalDependentStructures;
if (Node->HasExternalDependencies(&LocalDependentStructures))
{
for (UStruct* Struct : LocalDependentStructures)
{
ProcessHierarchy(Struct, Dependencies);
}
if (UK2Node_MacroInstance* MacroNode = Cast<UK2Node_MacroInstance>(Node))
{
if (UBlueprint* MacroSource = MacroNode->GetSourceBlueprint())
{
MacroSources.Add(MacroSource);
}
}
}
UK2Node_FunctionEntry* FunctionEntry = Cast<UK2Node_FunctionEntry>(Node);
if (FunctionEntry && !bIsDelegateSignatureGraph)
{
const FName FunctionName = (FunctionEntry->CustomGeneratedFunctionName != NAME_None)
? FunctionEntry->CustomGeneratedFunctionName
: FunctionEntry->FunctionReference.GetMemberName();
UFunction* ParentFunction = Blueprint->ParentClass ? Blueprint->ParentClass->FindFunctionByName(FunctionName) : nullptr;
if (ParentFunction && (UEdGraphSchema_K2::FN_UserConstructionScript != FunctionName))
{
ProcessHierarchy(ParentFunction, Dependencies);
}
}
// load Enums
for (UEdGraphPin* Pin : Node->Pins)
{
UObject* SubCategoryObject = Pin ? Pin->PinType.PinSubCategoryObject.Get() : nullptr;
if (SubCategoryObject && SubCategoryObject->IsA<UEnum>())
{
UBlueprint::ForceLoad(SubCategoryObject);
}
}
}
}
}
}
PreloadMacroSources(MacroSources);
PreloadInterfaces(Blueprint);
}
};
/**
Procedure used to remove old function implementations and child properties from data only blueprints.
These blueprints have a 'fast path' compilation path but we need to make sure that any data regenerated
by normal blueprint compilation is cleared here. If we don't then these functions and properties will
hang around when a class is converted from a real blueprint to a data only blueprint.
*/
void FBlueprintEditorUtils::RemoveStaleFunctions(UBlueprintGeneratedClass* Class, UBlueprint* Blueprint)
{
if (Class == nullptr)
{
return;
}
// Removes all existing functions from the class, currently used
TFieldIterator<UFunction> Fn(Class, EFieldIteratorFlags::ExcludeSuper);
if (Fn)
{
FString OrphanedClassString = FString::Printf(TEXT("ORPHANED_DATA_ONLY_%s"), *Class->GetName());
FName OrphanedClassName = MakeUniqueObjectName(GetTransientPackage(), UBlueprintGeneratedClass::StaticClass(), FName(*OrphanedClassString));
UClass* OrphanedClass = NewObject<UBlueprintGeneratedClass>(GetTransientPackage(), OrphanedClassName, RF_Public | RF_Transient);
OrphanedClass->CppClassStaticFunctions = Class->CppClassStaticFunctions;
OrphanedClass->ClassFlags |= CLASS_CompiledFromBlueprint;
OrphanedClass->ClassGeneratedBy = Class->ClassGeneratedBy;
const ERenameFlags RenFlags = REN_DontCreateRedirectors | REN_NonTransactional | REN_DoNotDirty;
while (Fn)
{
UFunction* Function = *Fn;
Class->RemoveFunctionFromFunctionMap(Function);
// invalidate this package's reference to this function, so
// subsequent packages that import it will treat it as if it didn't
// exist (because data-only blueprints shouldn't have functions)
// Note, Rename will remove the renamed object's linker when moving
// to a new package so invalidate the export beforehand
FLinkerLoad::InvalidateExport(Function);
Function->Rename(nullptr, OrphanedClass, RenFlags);
++Fn;
}
}
// Clear function map caches which will be rebuilt the next time functions are searched by name
Class->ClearFunctionMapsCaches();
Blueprint->GeneratedClass->Children = nullptr;
Blueprint->GeneratedClass->Bind();
Blueprint->GeneratedClass->StaticLink(true);
}
void FBlueprintEditorUtils::RefreshVariables(UBlueprint* Blueprint)
{
// module punchthrough:
IKismetCompilerInterface& Compiler = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>(KISMET_COMPILER_MODULENAME);
Compiler.RefreshVariables(Blueprint);
}
void FBlueprintEditorUtils::PreloadBlueprintSpecificData(UBlueprint* Blueprint)
{
TArray<UK2Node*> AllNodes;
FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, AllNodes);
for( UK2Node* K2Node : AllNodes )
{
K2Node->PreloadRequiredAssets();
}
}
void FBlueprintEditorUtils::LinkExternalDependencies(UBlueprint* Blueprint)
{
FRegenerationHelper::LinkExternalDependencies(Blueprint);
}
void FBlueprintEditorUtils::RecreateClassMetaData(UBlueprint* Blueprint, UClass* Class, bool bRemoveExistingMetaData)
{
if (!ensure(Blueprint && Class))
{
return;
}
UClass* ParentClass = Class->GetSuperClass();
TArray<FString> AllHideCategories;
if (bRemoveExistingMetaData)
{
Class->RemoveMetaData("HideCategories");
Class->RemoveMetaData("ShowCategories");
Class->RemoveMetaData("HideFunctions");
Class->RemoveMetaData("AutoExpandCategories");
Class->RemoveMetaData("AutoCollapseCategories");
Class->RemoveMetaData("PrioritizeCategories");
Class->RemoveMetaData("SparseClassDataTypes");
Class->RemoveMetaData("ClassGroupNames");
Class->RemoveMetaData("Category");
Class->RemoveMetaData(FBlueprintMetadata::MD_AllowableBlueprintVariableType);
}
if (ensure(ParentClass != nullptr))
{
if (!ParentClass->HasMetaData(FBlueprintMetadata::MD_IgnoreCategoryKeywordsInSubclasses))
{
// we want the categories just as they appear in the parent class
// (set bHomogenize to false) - especially since homogenization
// could inject spaces
FEditorCategoryUtils::GetClassHideCategories(ParentClass, AllHideCategories, /*bHomogenize =*/false);
if (ParentClass->HasMetaData(TEXT("ShowCategories")))
{
Class->SetMetaData(TEXT("ShowCategories"), *ParentClass->GetMetaData("ShowCategories"));
}
if (ParentClass->HasMetaData(TEXT("AutoExpandCategories")))
{
Class->SetMetaData(TEXT("AutoExpandCategories"), *ParentClass->GetMetaData("AutoExpandCategories"));
}
if (ParentClass->HasMetaData(TEXT("AutoCollapseCategories")))
{
Class->SetMetaData(TEXT("AutoCollapseCategories"), *ParentClass->GetMetaData("AutoCollapseCategories"));
}
if (ParentClass->HasMetaData(TEXT("PrioritizeCategories")))
{
Class->SetMetaData(TEXT("PrioritizeCategories"), *ParentClass->GetMetaData("PrioritizeCategories"));
}
}
if (ParentClass->HasMetaData(TEXT("HideFunctions")))
{
Class->SetMetaData(TEXT("HideFunctions"), *ParentClass->GetMetaData("HideFunctions"));
}
if (ParentClass->IsChildOf(UActorComponent::StaticClass()))
{
static const FName NAME_ClassGroupNames(TEXT("ClassGroupNames"));
Class->SetMetaData(FBlueprintMetadata::MD_BlueprintSpawnableComponent, TEXT("true"));
FString ClassGroupCategory = NSLOCTEXT("BlueprintableComponents", "CategoryName", "Custom").ToString();
if (!Blueprint->BlueprintCategory.IsEmpty())
{
ClassGroupCategory = Blueprint->BlueprintCategory;
}
Class->SetMetaData(NAME_ClassGroupNames, *ClassGroupCategory);
}
if (ParentClass->HasMetaData(TEXT("SparseClassDataTypes")))
{
Class->SetMetaData(TEXT("SparseClassDataTypes"), *ParentClass->GetMetaData("SparseClassDataTypes"));
}
}
// Add a category if one has been specified
if (Blueprint->BlueprintCategory.Len() > 0)
{
Class->SetMetaData(TEXT("Category"), *Blueprint->BlueprintCategory);
}
else
{
Class->RemoveMetaData(TEXT("Category"));
}
if ((Blueprint->BlueprintType == BPTYPE_Normal) ||
(Blueprint->BlueprintType == BPTYPE_Const) ||
(Blueprint->BlueprintType == BPTYPE_Interface))
{
Class->SetMetaData(FBlueprintMetadata::MD_AllowableBlueprintVariableType, TEXT("true"));
}
for (FString HideCategory : Blueprint->HideCategories)
{
TArray<TCHAR, FString::AllocatorType>& CharArray = HideCategory.GetCharArray();
int32 SpaceIndex = CharArray.Find(TEXT(' '));
while (SpaceIndex != INDEX_NONE)
{
CharArray.RemoveAt(SpaceIndex);
if (SpaceIndex >= CharArray.Num())
{
break;
}
TCHAR& WordStartingChar = CharArray[SpaceIndex];
WordStartingChar = FChar::ToUpper(WordStartingChar);
CharArray.Find(TEXT(' '), SpaceIndex);
}
AllHideCategories.Add(HideCategory);
}
if (AllHideCategories.Num() > 0)
{
Class->SetMetaData(TEXT("HideCategories"), *FString::Join(AllHideCategories, TEXT(" ")));
}
else
{
Class->RemoveMetaData(TEXT("HideCategories"));
}
}
void FBlueprintEditorUtils::PatchCDOSubobjectsIntoExport(UObject* PreviousCDO, UObject* NewCDO)
{
struct PatchCDOSubobjectsIntoExport_Impl
{
static void PatchSubObjects(UObject* OldObj, UObject* NewObj)
{
// Find all subobjects outered to the old object.
TArray<UObject*> OldSubObjects;
constexpr bool bIncludeNestedObjects = false;
GetObjectsWithOuter(OldObj, OldSubObjects, bIncludeNestedObjects);
// Exit now if we don't have any subobjects to process.
if (OldSubObjects.Num() == 0)
{
return;
}
// Build a lookup table for subobjects outered to the new object.
TMap<FName, UObject*> NewSubObjLookupTable;
NewSubObjLookupTable.Reserve(OldSubObjects.Num());
ForEachObjectWithOuter(NewObj, [&NewSubObjLookupTable](UObject* NewSubObj)
{
NewSubObjLookupTable.Add(NewSubObj->GetFName(), NewSubObj);
},
bIncludeNestedObjects);
// For each subobject that's outered to the old object, find the corresponding new subobject that matches its name and patch it into the export table.
// This includes any editinline subobjects that are instanced at edit time; the outer object (e.g. CDO) may not have a matching archetype in that case.
for (UObject* OldSubObj : OldSubObjects)
{
if (UObject** NewSubObjPtr = NewSubObjLookupTable.Find(OldSubObj->GetFName()))
{
UObject* NewSubObj = *NewSubObjPtr;
FLinkerLoad::PRIVATE_PatchNewObjectIntoExport(OldSubObj, NewSubObj);
}
else
{
// Invalidate any existing entry in the export table to avoid generation of implicit dependencies.
FLinkerLoad::InvalidateExport(OldSubObj);
}
}
}
};
if (PreviousCDO && NewCDO)
{
PatchCDOSubobjectsIntoExport_Impl::PatchSubObjects(PreviousCDO, NewCDO);
NewCDO->CheckDefaultSubobjects();
}
}
void FBlueprintEditorUtils::PropagateParentBlueprintDefaults(UClass* ClassToPropagate)
{
check(ClassToPropagate);
UObject* NewCDO = ClassToPropagate->GetDefaultObject();
check(NewCDO);
// Get the blueprint's BP derived lineage
TArray<UBlueprintGeneratedClass*> ParentBPStack;
UBlueprint::GetBlueprintHierarchyFromClass(ClassToPropagate, ParentBPStack);
// Starting from the least derived BP class, copy the properties into the new CDO
for(int32 Index = ParentBPStack.Num() - 1; Index > 0; --Index)
{
checkf(ParentBPStack[Index], TEXT("Parent classes for class %s have not yet been generated. Compile-on-load must be processed for the parent class first."), *ClassToPropagate->GetName());
UObject* LayerCDO = ParentBPStack[Index]->GetDefaultObject();
UEditorEngine::FCopyPropertiesForUnrelatedObjectsParams CopyDetails;
CopyDetails.bReplaceObjectClassReferences = false;
UEditorEngine::CopyPropertiesForUnrelatedObjects(LayerCDO, NewCDO, CopyDetails);
}
}
uint32 FBlueprintDuplicationScopeFlags::bStaticFlags = FBlueprintDuplicationScopeFlags::NoFlags;
void FBlueprintEditorUtils::PostDuplicateBlueprint(UBlueprint* Blueprint, bool bDuplicateForPIE)
{
TRACE_CPUPROFILER_EVENT_SCOPE(PostDuplicateBlueprint);
// Only recompile after duplication if this isn't PIE
if (!bDuplicateForPIE)
{
if(Blueprint->GeneratedClass != nullptr)
{
// Grab the old CDO, which contains the class defaults
UClass* OldBPGCAsClass = Blueprint->GeneratedClass;
UBlueprint* OldBlueprint = Cast<UBlueprint>(OldBPGCAsClass->ClassGeneratedBy);
UBlueprintGeneratedClass* OldBPGC = (UBlueprintGeneratedClass*)(OldBPGCAsClass);
UObject* OldCDO = OldBPGC->GetDefaultObject();
check(OldCDO != nullptr);
// Make sure that OldBPGC isn't garbage collected within this scope
struct FAddToRootHelper
{
FAddToRootHelper(UBlueprintGeneratedClass* InBPGC)
{
BPGC = InBPGC;
bWasRoot = BPGC->IsRooted();
if(!bWasRoot)
{
BPGC->AddToRoot();
}
}
~FAddToRootHelper()
{
if (!bWasRoot)
{
BPGC->RemoveFromRoot();
}
}
UBlueprintGeneratedClass* BPGC;
bool bWasRoot;
} KeepBPGCAlive(OldBPGC);
if (FBlueprintDuplicationScopeFlags::HasAnyFlag(FBlueprintDuplicationScopeFlags::ValidatePinsUsingSourceClass))
{
Blueprint->OriginalClass = OldBPGC;
}
// Grab the old class templates, which needs to be moved to the new class
USimpleConstructionScript* SCSRootNode = Blueprint->SimpleConstructionScript;
Blueprint->SimpleConstructionScript = nullptr;
UInheritableComponentHandler* InheritableComponentHandler = Blueprint->InheritableComponentHandler;
Blueprint->InheritableComponentHandler = nullptr;
TArray<UActorComponent*> Templates = Blueprint->ComponentTemplates;
Blueprint->ComponentTemplates.Empty();
TArray<UTimelineTemplate*> Timelines = Blueprint->Timelines;
Blueprint->Timelines.Empty();
Blueprint->GeneratedClass = nullptr;
Blueprint->SkeletonGeneratedClass = nullptr;
// Make sure the new blueprint has a shiny new class
FCompilerResultsLog Results;
FName NewSkelClassName, NewGenClassName;
Blueprint->GetBlueprintClassNames(NewGenClassName, NewSkelClassName);
UClass* NewClass = NewObject<UClass>(
Blueprint->GetOutermost(), Blueprint->GetBlueprintClass(), NewGenClassName, RF_Public | RF_Transactional);
Blueprint->GeneratedClass = NewClass;
NewClass->ClassGeneratedBy = Blueprint;
NewClass->SetSuperStruct(Blueprint->ParentClass);
Blueprint->bHasBeenRegenerated = true; // Set to true, similar to CreateBlueprint, since we've regerated the class by duplicating it
TMap<UObject*, UObject*> OldToNewMap;
UClass* NewBPGCAsClass = Blueprint->GeneratedClass;
UBlueprintGeneratedClass* NewBPGC = (UBlueprintGeneratedClass*)(NewBPGCAsClass);
if( SCSRootNode )
{
NewBPGC->SimpleConstructionScript = Cast<USimpleConstructionScript>(StaticDuplicateObject(SCSRootNode, NewBPGC, SCSRootNode->GetFName()));
Blueprint->SimpleConstructionScript = NewBPGC->SimpleConstructionScript;
const TArray<USCS_Node*>& AllNodes = NewBPGC->SimpleConstructionScript->GetAllNodes();
// Duplicate all component templates
for (USCS_Node* CurrentNode : AllNodes)
{
if (CurrentNode && CurrentNode->ComponentTemplate)
{
UActorComponent* DuplicatedComponent = CastChecked<UActorComponent>(StaticDuplicateObject(CurrentNode->ComponentTemplate, NewBPGC, CurrentNode->ComponentTemplate->GetFName()));
OldToNewMap.Add(CurrentNode->ComponentTemplate, DuplicatedComponent);
CurrentNode->ComponentTemplate = DuplicatedComponent;
}
}
if (USCS_Node* DefaultSceneRootNode = NewBPGC->SimpleConstructionScript->GetDefaultSceneRootNode())
{
if (!AllNodes.Contains(DefaultSceneRootNode) && DefaultSceneRootNode->ComponentTemplate)
{
UActorComponent* DuplicatedComponent = Cast<UActorComponent>(OldToNewMap.FindRef(DefaultSceneRootNode->ComponentTemplate));
if (!DuplicatedComponent)
{
DuplicatedComponent = CastChecked<UActorComponent>(StaticDuplicateObject(DefaultSceneRootNode->ComponentTemplate, NewBPGC, DefaultSceneRootNode->ComponentTemplate->GetFName()));
OldToNewMap.Add(DefaultSceneRootNode->ComponentTemplate, DuplicatedComponent);
}
DefaultSceneRootNode->ComponentTemplate = DuplicatedComponent;
}
}
}
for (UActorComponent* OldComponent : Templates)
{
UActorComponent* NewComponent = CastChecked<UActorComponent>(StaticDuplicateObject(OldComponent, NewBPGC, OldComponent->GetFName()));
NewBPGC->ComponentTemplates.Add(NewComponent);
OldToNewMap.Add(OldComponent, NewComponent);
}
for (UTimelineTemplate* OldTimeline : Timelines)
{
UTimelineTemplate* NewTimeline = CastChecked<UTimelineTemplate>(StaticDuplicateObject(OldTimeline, NewBPGC, OldTimeline->GetFName()));
if (FBlueprintDuplicationScopeFlags::HasAnyFlag(FBlueprintDuplicationScopeFlags::TheSameTimelineGuid))
{
NewTimeline->TimelineGuid = OldTimeline->TimelineGuid;
// Ensure that cached names sync back up with the original GUID.
FUpdateTimelineCachedNames::Execute(NewTimeline);
}
NewBPGC->Timelines.Add(NewTimeline);
OldToNewMap.Add(OldTimeline, NewTimeline);
}
if (InheritableComponentHandler)
{
NewBPGC->InheritableComponentHandler = Cast<UInheritableComponentHandler>(StaticDuplicateObject(InheritableComponentHandler, NewBPGC, InheritableComponentHandler->GetFName()));
if (NewBPGC->InheritableComponentHandler)
{
NewBPGC->InheritableComponentHandler->UpdateOwnerClass(NewBPGC);
}
}
Blueprint->ComponentTemplates = NewBPGC->ComponentTemplates;
Blueprint->Timelines = NewBPGC->Timelines;
Blueprint->InheritableComponentHandler = NewBPGC->InheritableComponentHandler;
FBlueprintCompilationManager::CompileSynchronously(
FBPCompileRequest(Blueprint, EBlueprintCompileOptions::RegenerateSkeletonOnly, nullptr)
);
// Create a new blueprint guid
Blueprint->GenerateNewGuid();
// Give all member variables a new guid
TMap<FGuid, FGuid> NewVarGuids;
for (FBPVariableDescription& Var : Blueprint->NewVariables)
{
Var.VarGuid = NewVarGuids.Emplace(Var.VarGuid, FGuid::NewGuid());
}
TArray< UEdGraphNode* > AllGraphNodes;
GetAllNodesOfClass(Blueprint, AllGraphNodes);
// Before we update Guids, we can use them to dupe breakpoints, watchpins to the new BP
FKismetDebugUtilities::PostDuplicateBlueprint(OldBlueprint, Blueprint, AllGraphNodes);
// Give all nodes a new Guid
for(UEdGraphNode* Node : AllGraphNodes)
{
if (!FBlueprintDuplicationScopeFlags::HasAnyFlag(FBlueprintDuplicationScopeFlags::TheSameNodeGuid))
{
Node->CreateNewGuid();
}
// Some variable & delegate nodes must be fixed up on duplicate, this cannot wait for individual
// node calls to PostDuplicate because it happens after compilation and will still result in errors
UK2Node_Variable* VariableNode = Cast<UK2Node_Variable>(Node);
UK2Node_BaseMCDelegate* DelegateNode = Cast<UK2Node_BaseMCDelegate>(Node);
if(VariableNode || DelegateNode)
{
FMemberReference& OutdatedReference = VariableNode ? VariableNode->VariableReference : DelegateNode->DelegateReference;
UK2Node* OutdatedNode = Cast<UK2Node>(Node);
// Self context variable nodes need to be updated with the new Blueprint class
if(OutdatedReference.IsSelfContext() || OutdatedReference.GetMemberParentClass() != OutdatedNode->GetBlueprintClassFromNode())
{
// update variable references with new Guids if necessary
if (FGuid* NewGuid = NewVarGuids.Find(OutdatedReference.GetMemberGuid()))
{
if (OutdatedReference.IsSelfContext())
{
OutdatedReference.SetSelfMember(OutdatedReference.GetMemberName(), *NewGuid);
}
else
{
OutdatedReference.SetExternalMember(OutdatedReference.GetMemberName(), OutdatedNode->GetBlueprintClassFromNode());
}
}
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
if(UEdGraphPin* SelfPin = K2Schema->FindSelfPin(*OutdatedNode, EGPD_Input))
{
UClass* TargetClass = nullptr;
if(FProperty* Property = OutdatedReference.ResolveMember<FProperty>(OutdatedNode->GetBlueprintClassFromNode()))
{
// Properties that are owned by a Struct - e.g. sparse class data - will not have a class:
if (UClass* OwningClass = Property->GetOwnerClass())
{
TargetClass = OwningClass->GetAuthoritativeClass();
}
}
else
{
TargetClass = NewClass;
}
SelfPin->PinType.PinSubCategoryObject = TargetClass;
}
}
}
}
// Skip CDO validation in this case as we will not have yet propagated values to the new CDO. Also skip
// Blueprint search data updates, as that will be handled by an OnAssetAdded() delegate in the FiB manager.
const EBlueprintCompileOptions BPCompileOptions =
EBlueprintCompileOptions::SkipDefaultObjectValidation |
EBlueprintCompileOptions::SkipFiBSearchMetaUpdate;
FBlueprintCompilationManager::CompileSynchronously(
FBPCompileRequest(Blueprint, BPCompileOptions, nullptr)
);
FArchiveReplaceObjectRef<UObject> ReplaceTemplateRefs(NewBPGC, OldToNewMap);
// Now propagate the values from the old CDO to the new one
check(Blueprint->SkeletonGeneratedClass != nullptr);
UObject* NewCDO = Blueprint->GeneratedClass->GetDefaultObject();
check(NewCDO != nullptr);
TMap<UClass*, UClass*> InOutOldToNewClassMap;
TMap<UObject*, UObject*> CreatedInstanceMap;
TArray< TTuple<UObject*, UObject*>> OrderedListOfObjectToCopy;
FBlueprintCompileReinstancer::PreCreateSubObjectsForReinstantiation(InOutOldToNewClassMap, OldCDO, NewCDO, CreatedInstanceMap, nullptr, &OrderedListOfObjectToCopy);
// We only need to copy properties of the pre-created instances, the rest of the default sub object is done inside the UEditorEngine::CopyPropertiesForUnrelatedObjects
TMap<UObject*, UObject*> OldToNewInstanceMap(CreatedInstanceMap);
UEngine::FCopyPropertiesForUnrelatedObjectsParams Params;
Params.OptionalReplacementMappings = &OldToNewInstanceMap;
Params.bOnlyHandleDirectSubObjects = true;
Params.bReplaceInternalReferenceUponRead = true;
for (const auto& Pair : OrderedListOfObjectToCopy)
{
UEditorEngine::CopyPropertiesForUnrelatedObjects(Pair.Key, Pair.Value, Params);
}
// copy sparse data over to the new class sparse data, if any:
const TObjectPtr<UScriptStruct> SparseData = Blueprint->GeneratedClass->GetSparseClassDataStruct();
if (SparseData && OldBPGC->bIsSparseClassDataSerializable)
{
void* SparseDataInstance = Blueprint->GeneratedClass->GetOrCreateSparseClassData();
// Compile may have generated a new sparse class data, in which case we're just going to
// use whatever the compiler generated - if we're reusing the source class's sparse
// class data then we can copy over the values immediately.. We could CPFUO here
// as well, but if the compiler generated the sparse data that could be undesirable - e.g.
// because the sparse data is caching information about the CDO or Class
const TObjectPtr<UScriptStruct> OldSparseData = OldBPGC->GetSparseClassDataStruct();
if (OldSparseData == SparseData)
{
const void* OldSparseDataInstance = OldBPGC->GetSparseClassData(EGetSparseClassDataMethod::ReturnIfNull);
if (OldSparseDataInstance && ensure(OldSparseDataInstance != SparseDataInstance))
{
SparseData->CopyScriptStruct(SparseDataInstance, OldSparseDataInstance);
}
}
NewBPGC->bIsSparseClassDataSerializable = true; // match the object we're being duplicated from
}
FBlueprintEditorUtils::ReconstructAllNodes(Blueprint);
if (!FBlueprintDuplicationScopeFlags::HasAnyFlag(FBlueprintDuplicationScopeFlags::NoExtraCompilation))
{
// And compile again to make sure they go into the generated class, get cleaned up, etc...
FKismetEditorUtilities::CompileBlueprint(Blueprint, EBlueprintCompileOptions::SkipGarbageCollection);
}
}
// it can still keeps references to some external objects
Blueprint->LastEditedDocuments.Empty();
}
// Should be no instances of this new blueprint, so no need to replace any
}
void FBlueprintEditorUtils::RemoveGeneratedClasses(UBlueprint* Blueprint)
{
IKismetCompilerInterface& Compiler = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>(KISMET_COMPILER_MODULENAME);
Compiler.RemoveBlueprintGeneratedClasses(Blueprint);
}
void FBlueprintEditorUtils::UpdateDelegatesInBlueprint(UBlueprint* Blueprint)
{
check(Blueprint);
TArray<UEdGraph*> Graphs;
Blueprint->GetAllGraphs(Graphs);
for (UEdGraph* Graph : Graphs)
{
if(!IsGraphIntermediate(Graph))
{
TArray<UK2Node_CreateDelegate*> CreateDelegateNodes;
Graph->GetNodesOfClass(CreateDelegateNodes);
for (UK2Node_CreateDelegate* DelegateNode: CreateDelegateNodes)
{
DelegateNode->HandleAnyChangeWithoutNotifying();
}
TArray<UK2Node_Event*> EventNodes;
Graph->GetNodesOfClass(EventNodes);
for (UK2Node_Event* EventNode : EventNodes)
{
EventNode->UpdateDelegatePin();
}
TArray<UK2Node_Knot*> Knots;
Graph->GetNodesOfClass(Knots);
for (UK2Node_Knot* Knot : Knots)
{
// Indiscriminate reuse of UK2Node_Knot::PostReconstructNode() is the convention established
// by UEdGraphSchema_K2::OnPinConnectionDoubleCicked. This forces the pin type data to be
// refreshed (e.g. due to changes in UpdateDelegatePin())
Knot->PostReconstructNode();
}
}
}
}
// Blueprint has materially changed. Recompile the skeleton, notify observers, and mark the package as dirty.
void FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(UBlueprint* Blueprint)
{
// The Blueprint has been structurally modified and this means that some node titles will need to be refreshed
GetDefault<UEdGraphSchema_K2>()->ForceVisualizationCacheClear();
Blueprint->bCachedDependenciesUpToDate = false;
if (Blueprint->Status != BS_BeingCreated && !Blueprint->bBeingCompiled)
{
BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_MarkBlueprintasStructurallyModified);
FBlueprintCompilationManager::CompileSynchronously(
FBPCompileRequest(Blueprint, EBlueprintCompileOptions::RegenerateSkeletonOnly, nullptr)
);
// Call general modification callback as well
MarkBlueprintAsModified(Blueprint);
{
BP_SCOPED_COMPILER_EVENT_STAT(EKismetCompilerStats_NotifyBlueprintChanged);
// Notify any interested parties that the blueprint has changed
Blueprint->BroadcastChanged();
}
}
}
// Blueprint has changed in some manner that invalidates the compiled data (link made/broken, default value changed, etc...)
void FBlueprintEditorUtils::MarkBlueprintAsModified(UBlueprint* Blueprint, FPropertyChangedEvent PropertyChangedEvent)
{
if(Blueprint->bBeingCompiled)
{
return;
}
Blueprint->bCachedDependenciesUpToDate = false;
if (Blueprint->Status != BS_BeingCreated)
{
// This clears any cached data, which includes the macro tunnel node data
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
for (UEdGraph* GraphToClear : AllGraphs)
{
for (UEdGraphNode* Node : GraphToClear->Nodes)
{
if (UK2Node* BPNode = Cast<UK2Node>(Node))
{
BPNode->ClearCachedBlueprintData(Blueprint);
}
}
}
// If this was called the CDO was probably modified. Regenerate the post construct property list
UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(Blueprint->GeneratedClass);
if (!Blueprint->bBeingCompiled && BPGC)
{
BPGC->UpdateCustomPropertyListForPostConstruction();
TArray<UClass*> ChildClasses;
GetDerivedClasses(BPGC, ChildClasses);
for (UClass* ChildClass : ChildClasses)
{
CastChecked<UBlueprintGeneratedClass>(ChildClass)->UpdateCustomPropertyListForPostConstruction();
}
}
Blueprint->Status = BS_Dirty;
Blueprint->MarkPackageDirty();
// Previously, PostEditChange() was called on the Blueprint which creates an empty FPropertyChangedEvent. In
// certain cases, we needed to be able to pass along specific FPropertyChangedEvent that initially triggered
// this call so that we could keep the Blueprint from refreshing under certain conditions.
Blueprint->PostEditChangeProperty(PropertyChangedEvent);
// Clear out the cache as the user may have added or removed a latent action to a macro graph
FBlueprintEditorUtils::ClearMacroCosmeticInfoCache(Blueprint);
}
if (GEditor)
{
IAssetEditorInstance* AssetEditor = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(Blueprint, false);
if (AssetEditor)
{
// Prevent crash with the custom editor operating on the project-specific UBlueprint class
// Such custom editor might not inherit after FBlueprintEditor, but could still utilize FBlueprintEditorUtils
FAssetEditorToolkit* AssetEditorToolkit = static_cast<FAssetEditorToolkit*>(AssetEditor);
if (AssetEditorToolkit->IsBlueprintEditor())
{
FBlueprintEditor* BlueprintEditor = static_cast<FBlueprintEditor*>(AssetEditor);
BlueprintEditor->UpdateNodesUnrelatedStatesAfterGraphChange();
}
}
}
}
bool FBlueprintEditorUtils::ShouldRegenerateBlueprint(UBlueprint* Blueprint)
{
return !IsCompileOnLoadDisabled(Blueprint)
&& Blueprint->bRecompileOnLoad
&& !Blueprint->bIsRegeneratingOnLoad;
}
bool FBlueprintEditorUtils::IsCompileOnLoadDisabled(UBlueprint* Blueprint)
{
bool bCompilationDisabled = !Blueprint->bRecompileOnLoad;
if(bCompilationDisabled)
{
// bRecompileOnLoad has been unused for awhile, warn once about its use,
// in case it is unintentional or the user has forgotten they've disabled
// it:
static bool bHasWarned = false;
if(!bHasWarned)
{
bHasWarned = true;
UE_LOG(LogBlueprint, Warning,
TEXT("Blueprints are loading but RecompileOnLoad is disabled - no bytecode will be available unless SkipByteCodeSerialization is disabled"));
}
}
if(Blueprint->GetLinker())
{
bCompilationDisabled = (Blueprint->GetLinker()->LoadFlags & LOAD_DisableCompileOnLoad) != LOAD_None;
}
// if the blueprint's package was cooked for editor builds we cannot recompile it as duplication will crash and since
// it's already cooked, if will already be up to date (likely they shouldn't exist, but in case they do we need to make them work)
if (Blueprint->GetOutermost()->bIsCookedForEditor)
{
bCompilationDisabled = true;
}
return bCompilationDisabled;
}
// Helper function to get the blueprint that ultimately owns a node.
UBlueprint* FBlueprintEditorUtils::FindBlueprintForNode(const UEdGraphNode* Node)
{
UEdGraph* Graph = Node ? Cast<UEdGraph>(Node->GetOuter()) : nullptr;
return FindBlueprintForGraph(Graph);
}
// Helper function to get the blueprint that ultimately owns a node. Cannot fail.
UBlueprint* FBlueprintEditorUtils::FindBlueprintForNodeChecked(const UEdGraphNode* Node)
{
UBlueprint* Result = FindBlueprintForNode(Node);
checkf(Result, TEXT("FBlueprintEditorUtils::FindBlueprintForNodeChecked(%s) failed to find a Blueprint."), *GetPathNameSafe(Node));
return Result;
}
// Helper function to get the blueprint that ultimately owns a graph.
UBlueprint* FBlueprintEditorUtils::FindBlueprintForGraph(const UEdGraph* Graph)
{
for (UObject* TestOuter = Graph ? Graph->GetOuter() : nullptr; TestOuter; TestOuter = TestOuter->GetOuter())
{
if (UBlueprint* Result = Cast<UBlueprint>(TestOuter))
{
return Result;
}
}
return nullptr;
}
// Helper function to get the blueprint that ultimately owns a graph. Cannot fail.
UBlueprint* FBlueprintEditorUtils::FindBlueprintForGraphChecked(const UEdGraph* Graph)
{
UBlueprint* Result = FBlueprintEditorUtils::FindBlueprintForGraph(Graph);
checkf(Result, TEXT("FBlueprintEditorUtils::FindBlueprintForGraphChecked(%s) failed to find a Blueprint."), *GetPathNameSafe(Graph));
return Result;
}
UClass* FBlueprintEditorUtils::GetSkeletonClass(UClass* FromClass)
{
if (FromClass)
{
if (UBlueprint* Generator = Cast<UBlueprint>(FromClass->ClassGeneratedBy))
{
return Generator->SkeletonGeneratedClass;
}
}
return nullptr;
}
const UClass* FBlueprintEditorUtils::GetSkeletonClass(const UClass* FromClass)
{
return GetSkeletonClass(const_cast<UClass*>(FromClass));
}
UClass* FBlueprintEditorUtils::GetMostUpToDateClass(UClass* FromClass)
{
if (!FromClass || FromClass->HasAnyClassFlags(CLASS_Native) || FromClass->bCooked)
{
return FromClass;
}
// It's really not safe/coherent to try and dig out the 'right' class. Things that need the 'most up to date'
// version of a class should always be looking at the skeleton:
UClass* SkeletonClass = GetSkeletonClass(FromClass);
return SkeletonClass ? SkeletonClass : FromClass;
}
const UClass* FBlueprintEditorUtils::GetMostUpToDateClass(const UClass* FromClass)
{
return GetMostUpToDateClass(const_cast<UClass*>(FromClass));
}
bool FBlueprintEditorUtils::PropertyStillExists(FProperty* Property)
{
return GetMostUpToDateProperty(Property) != nullptr;
}
FProperty* FBlueprintEditorUtils::GetMostUpToDateProperty(FProperty* Property)
{
if(const UClass* OwningClass = Property->GetTypedOwner<UClass>())
{
const UClass* UpToDateClass = GetMostUpToDateClass(OwningClass);
if (UpToDateClass && UpToDateClass != OwningClass)
{
Property = UpToDateClass->FindPropertyByName(Property->GetFName());
}
}
return Property;
}
const FProperty* FBlueprintEditorUtils::GetMostUpToDateProperty(const FProperty* Property)
{
return GetMostUpToDateProperty(const_cast<FProperty*>(Property));
}
UFunction* FBlueprintEditorUtils::GetMostUpToDateFunction(UFunction* Function)
{
if(const UClass* OwningClass = Function->GetTypedOuter<UClass>())
{
const UClass* UpToDateClass = GetMostUpToDateClass(OwningClass);
if (UpToDateClass && UpToDateClass != OwningClass)
{
Function = UpToDateClass->FindFunctionByName(Function->GetFName());
}
}
return Function;
}
const UFunction* FBlueprintEditorUtils::GetMostUpToDateFunction(const UFunction* Function)
{
return GetMostUpToDateFunction(const_cast<UFunction*>(Function));
}
bool FBlueprintEditorUtils::IsGraphNameUnique(UObject* InOuter, const FName& InName)
{
// Check for any object directly created in the blueprint
if( !FindObject<UObject>(InOuter, *InName.ToString()) )
{
if(UBlueprint* Blueprint = Cast<UBlueprint>(InOuter))
{
// Next, check for functions with that name in the blueprint's class scope
FFieldVariant ExistingField = FindUFieldOrFProperty(Blueprint->SkeletonGeneratedClass, InName);
if( !ExistingField )
{
// Finally, check function entry points
TArray<UK2Node_Event*> AllEvents;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Event>(Blueprint, AllEvents);
for(int32 i=0; i < AllEvents.Num(); i++)
{
UK2Node_Event* EventNode = AllEvents[i];
check(EventNode);
if( EventNode->CustomFunctionName == InName
|| EventNode->EventReference.GetMemberName() == InName )
{
return false;
}
}
// All good!
return true;
}
}
// All good!
return true;
}
return false;
}
UEdGraph* FBlueprintEditorUtils::CreateNewGraph(UObject* ParentScope, const FName& GraphName, TSubclassOf<class UEdGraph> GraphClass, TSubclassOf<class UEdGraphSchema> SchemaClass)
{
UEdGraph* NewGraph = nullptr;
bool bRename = false;
// Ensure this name isn't already being used for a graph
if (GraphName != NAME_None)
{
if (UObject* ExistingObject = FindObject<UObject>(ParentScope, *(GraphName.ToString())))
{
if (ExistingObject->IsA<UEdGraph>())
{
// Rename the old graph out of the way - this may confuse the user somewhat - and even
// break their logic. But name collisions are not avoidable e.g. someone can add
// a function to an interface that conflicts with something in a class hierarchy
ExistingObject->Rename(nullptr, ExistingObject->GetOuter(), REN_DoNotDirty);
}
else if (ExistingObject->IsA<UObjectRedirector>())
{
const UBlueprint* Blueprint = Cast<UBlueprint>(ParentScope);
if (Blueprint && Blueprint->BlueprintType == BPTYPE_MacroLibrary)
{
// When renaming a graph inside a macro library, we may have dropped a redirector after a previous
// rename (see RenameGraph). If we're now reusing it, move the redirector aside to free up the name.
ExistingObject->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors);
}
}
}
// Construct new graph with the supplied name
NewGraph = NewObject<UEdGraph>(ParentScope, GraphClass, NAME_None, RF_Transactional);
bRename = true;
}
else
{
// Construct a new graph with a default name
NewGraph = NewObject<UEdGraph>(ParentScope, GraphClass, NAME_None, RF_Transactional);
}
NewGraph->Schema = SchemaClass;
// Now move to where we want it to. Workaround to ensure transaction buffer is correctly utilized
if (bRename)
{
NewGraph->Rename(*(GraphName.ToString()), ParentScope, REN_DoNotDirty);
}
return NewGraph;
}
void FBlueprintEditorUtils::CreateMatchingFunction(UK2Node_CallFunction* InNode, TSubclassOf<class UEdGraphSchema> InSchemaClass)
{
if (UBlueprint* Blueprint = InNode->GetBlueprint())
{
FScopedTransaction Transaction(LOCTEXT("CreateMatchingFunction", "Create Matching Function"));
Blueprint->Modify();
UEdGraph* Graph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, InNode->GetFunctionName(), UEdGraph::StaticClass(), InSchemaClass);
FBlueprintEditorUtils::AddFunctionGraph<UClass>(Blueprint, Graph, true, nullptr);
TArray<UK2Node_FunctionEntry*> Entry;
Graph->GetNodesOfClass<UK2Node_FunctionEntry>(Entry);
if (ensure(Entry.Num() == 1))
{
UK2Node_FunctionResult* Result = nullptr;
for (UEdGraphPin* Pin : InNode->Pins)
{
// if this wasn't a split pin
if (!Pin->ParentPin)
{
FName PinName = Pin->GetFName();
// If this isn't a default pin, add it to the function entry
if (PinName != UEdGraphSchema_K2::PN_Self && PinName != UEdGraphSchema_K2::PN_Execute && PinName != UEdGraphSchema_K2::PN_Then)
{
if (Pin->Direction == EEdGraphPinDirection::EGPD_Input)
{
// add as an input param to function
Entry[0]->CreateUserDefinedPin(PinName, Pin->PinType, EEdGraphPinDirection::EGPD_Output);
}
else if (Pin->Direction == EEdGraphPinDirection::EGPD_Output)
{
// only create a result node if there are out parameters
if (!Result)
{
Result = FBlueprintEditorUtils::FindOrCreateFunctionResultNode(Entry[0]);
}
// add as an output param to function
Result->CreateUserDefinedPin(PinName, Pin->PinType, EEdGraphPinDirection::EGPD_Input);
}
}
}
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
InNode->ReconstructNode();
}
}
bool FBlueprintEditorUtils::IsFunctionConvertableToEvent(UBlueprint* const BlueprintObj, UFunction* const Function)
{
return BlueprintObj && BlueprintObj->BlueprintType != BPTYPE_FunctionLibrary && BlueprintObj->BlueprintType != BPTYPE_Interface && Function && !HasFunctionBlueprintThreadSafeMetaData(Function);
}
UFunction* FBlueprintEditorUtils::FindFunctionInImplementedInterfaces(const UBlueprint* Blueprint, const FName& FunctionName, bool * bOutInvalidInterface, bool bGetAllInterfaces)
{
if(Blueprint)
{
TArray<UClass*> InterfaceClasses;
FindImplementedInterfaces(Blueprint, bGetAllInterfaces, InterfaceClasses);
if( bOutInvalidInterface )
{
*bOutInvalidInterface = false;
}
// Now loop through the interface classes and try and find the function
for (UClass* SearchClass : InterfaceClasses)
{
if( SearchClass )
{
// Use the skeleton class if possible, as the generated class may not always be up-to-date (e.g. if the compile state is dirty).
UBlueprint* InterfaceBlueprint = Cast<UBlueprint>(SearchClass->ClassGeneratedBy);
if (InterfaceBlueprint && InterfaceBlueprint->SkeletonGeneratedClass)
{
SearchClass = InterfaceBlueprint->SkeletonGeneratedClass;
}
do
{
if( UFunction* OverriddenFunction = SearchClass->FindFunctionByName(FunctionName, EIncludeSuperFlag::ExcludeSuper) )
{
return OverriddenFunction;
}
SearchClass = SearchClass->GetSuperClass();
} while (SearchClass);
}
else if( bOutInvalidInterface )
{
*bOutInvalidInterface = true;
}
}
}
return nullptr;
}
void FBlueprintEditorUtils::FindImplementedInterfaces(const UBlueprint* Blueprint, bool bGetAllInterfaces, TArray<UClass*>& ImplementedInterfaces)
{
// First get the ones this blueprint implemented
for (const FBPInterfaceDescription& ImplementedInterface : Blueprint->ImplementedInterfaces)
{
ImplementedInterfaces.AddUnique(ImplementedInterface.Interface);
}
if (bGetAllInterfaces)
{
// Now get all the ones the blueprint's parents implemented
UClass* BlueprintParent = Blueprint->ParentClass;
while (BlueprintParent)
{
for (const FImplementedInterface& ImplementedInterface : BlueprintParent->Interfaces)
{
ImplementedInterfaces.AddUnique(ImplementedInterface.Class);
}
BlueprintParent = BlueprintParent->GetSuperClass();
}
}
}
bool FBlueprintEditorUtils::ImplementsInterface(const UBlueprint* Blueprint, bool bIncludeInherited, UClass* InterfaceClass)
{
// First test this blueprint
if (Blueprint->ImplementedInterfaces.ContainsByPredicate([InterfaceClass](const FBPInterfaceDescription& ImplementedInterface){ return ImplementedInterface.Interface->IsChildOf(InterfaceClass); }))
{
return true;
}
if (bIncludeInherited)
{
if (UClass* BlueprintParent = Blueprint->ParentClass)
{
return BlueprintParent->ImplementsInterface(InterfaceClass);
}
}
return false;
}
UClass* const FBlueprintEditorUtils::GetOverrideFunctionClass(UBlueprint* Blueprint, const FName FuncName, UFunction** OutFunction)
{
if (!Blueprint->SkeletonGeneratedClass)
{
return nullptr;
}
UFunction* OverrideFunc = FBlueprintEditorUtils::GetInterfaceFunction(Blueprint, FuncName);
if (OverrideFunc == nullptr)
{
OverrideFunc = FindUField<UFunction>(Blueprint->SkeletonGeneratedClass, FuncName);
// search up the class hierarchy, we want to find the original declaration of the function to match FBlueprintEventNodeSpawner.
// Doing so ensures that we can find the existing node if there is one:
const UClass* Iter = Blueprint->SkeletonGeneratedClass->GetSuperClass();
while (Iter != nullptr && OverrideFunc == nullptr)
{
if (UFunction * F = Iter->FindFunctionByName(FuncName))
{
OverrideFunc = F;
}
else
{
break;
}
Iter = Iter->GetSuperClass();
}
}
if (OutFunction != nullptr)
{
*OutFunction = OverrideFunc;
}
return (OverrideFunc ? CastChecked<UClass>(OverrideFunc->GetOuter())->GetAuthoritativeClass() : nullptr);
}
void FBlueprintEditorUtils::AddMacroGraph( UBlueprint* Blueprint, class UEdGraph* Graph, bool bIsUserCreated, UClass* SignatureFromClass )
{
// Give the schema a chance to fill out any required nodes (like the entry node or results node)
const UEdGraphSchema* Schema = Graph->GetSchema();
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(Graph->GetSchema());
Schema->CreateDefaultNodesForGraph(*Graph);
if (K2Schema != nullptr)
{
K2Schema->CreateMacroGraphTerminators(*Graph, SignatureFromClass);
if (bIsUserCreated)
{
// We need to flag the entry node to make sure that the compiled function is callable from Kismet2
K2Schema->AddExtraFunctionFlags(Graph, (FUNC_BlueprintCallable|FUNC_BlueprintEvent));
K2Schema->MarkFunctionEntryAsEditable(Graph, true);
}
}
// Mark the graph as public if it's going to be referenced directly from other blueprints
if (Blueprint->BlueprintType == BPTYPE_MacroLibrary)
{
Graph->SetFlags(RF_Public);
}
Blueprint->MacroGraphs.Add(Graph);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
void FBlueprintEditorUtils::AddInterfaceGraph(UBlueprint* Blueprint, class UEdGraph* Graph, UClass* InterfaceClass)
{
const UEdGraphSchema_K2* K2Schema = Cast<const UEdGraphSchema_K2>(Graph->GetSchema());
if (K2Schema != nullptr)
{
K2Schema->CreateFunctionGraphTerminators(*Graph, InterfaceClass);
}
}
void FBlueprintEditorUtils::AddUbergraphPage(UBlueprint* Blueprint, class UEdGraph* Graph)
{
#if WITH_EDITORONLY_DATA
Blueprint->UbergraphPages.Add(Graph);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
#endif //#if WITH_EDITORONLY_DATA
}
FName FBlueprintEditorUtils::GetUbergraphFunctionName(const UBlueprint* ForBlueprint)
{
const FString UbergraphCallString = UEdGraphSchema_K2::FN_ExecuteUbergraphBase.ToString() + TEXT("_") + ForBlueprint->GetName();
return FName(*UbergraphCallString);
}
void FBlueprintEditorUtils::AddDomainSpecificGraph(UBlueprint* Blueprint, class UEdGraph* Graph)
{
// Give the schema a chance to fill out any required nodes (like the entry node or results node)
const UEdGraphSchema* Schema = Graph->GetSchema();
Schema->CreateDefaultNodesForGraph(*Graph);
check(Blueprint->BlueprintType != BPTYPE_MacroLibrary);
#if WITH_EDITORONLY_DATA
Blueprint->FunctionGraphs.Add(Graph);
#endif //#if WITH_EDITORONLY_DATA
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
// Remove the supplied set of graphs from the Blueprint.
void FBlueprintEditorUtils::RemoveGraphs( UBlueprint* Blueprint, const TArray<class UEdGraph*>& GraphsToRemove )
{
for (int32 ItemIndex=0; ItemIndex < GraphsToRemove.Num(); ++ItemIndex)
{
UEdGraph* Graph = GraphsToRemove[ItemIndex];
FBlueprintEditorUtils::RemoveGraph(Blueprint, Graph, EGraphRemoveFlags::MarkTransient);
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
namespace UE::Private
{
// Given a function graph, check if it's field notify and remove its name from the meta data of all field notify variables.
static void RemoveFieldNotifyFunctionFromAllMetaData(UBlueprint* Blueprint, class UEdGraph* FunctionGraphToRemove)
{
if (Blueprint->FunctionGraphs.Contains(FunctionGraphToRemove) && FBlueprintEditorUtils::ImplementsInterface(Blueprint, true, UNotifyFieldValueChanged::StaticClass()))
{
for (UEdGraphNode* Node : FunctionGraphToRemove->Nodes)
{
if (UK2Node_FunctionEntry* NodeFunctionEntry = Cast<UK2Node_FunctionEntry>(Node))
{
if (NodeFunctionEntry->MetaData.HasMetaData(FBlueprintMetadata::MD_FieldNotify))
{
FBlueprintEditorUtils::RemoveFieldNotifyFromAllMetadata(Blueprint, FunctionGraphToRemove->GetFName());
}
break;
}
}
}
}
}
// Removes the supplied graph from the Blueprint.
void FBlueprintEditorUtils::RemoveGraph(UBlueprint* Blueprint, class UEdGraph* GraphToRemove, EGraphRemoveFlags::Type Flags /*= Transient | Recompile */)
{
GraphToRemove->Modify();
for (UObject* TestOuter = GraphToRemove->GetOuter(); TestOuter; TestOuter = TestOuter->GetOuter())
{
if (TestOuter == Blueprint)
{
UE::Private::RemoveFieldNotifyFunctionFromAllMetaData(Blueprint, GraphToRemove);
Blueprint->DelegateSignatureGraphs.Remove( GraphToRemove );
Blueprint->FunctionGraphs.Remove( GraphToRemove );
Blueprint->UbergraphPages.Remove( GraphToRemove );
// Can't just call Remove, the object is wrapped in a struct
for(int EditedDocIdx = 0; EditedDocIdx < Blueprint->LastEditedDocuments.Num(); ++EditedDocIdx)
{
if(Blueprint->LastEditedDocuments[EditedDocIdx].EditedObjectPath.ResolveObject() == GraphToRemove)
{
Blueprint->LastEditedDocuments.RemoveAt(EditedDocIdx);
break;
}
}
if(Blueprint->MacroGraphs.Remove( GraphToRemove ) > 0 )
{
//removes all macro nodes using this macro graph
TArray<UK2Node_MacroInstance*> MacroNodes;
FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, MacroNodes);
for (UK2Node_MacroInstance* Node : MacroNodes)
{
if(Node->GetMacroGraph() == GraphToRemove)
{
FBlueprintEditorUtils::RemoveNode(Blueprint, Node);
}
}
// Clear the cache since it's indexed by graph and one of the graphs is going away
FBlueprintEditorUtils::ClearMacroCosmeticInfoCache(Blueprint);
// Clear redirectors to the graph - we may have created these for macro graphs.
// See comment related to macro graphs in FBlueprintEditorUtils::RenameGraph
// involving conditional assignment of REN_DontCreateRedirectors
TArray<UObject*> Inners;
GetObjectsWithOuter(Blueprint, Inners, false);
for(UObject* Object : Inners)
{
if(UObjectRedirector* Redirector = Cast<UObjectRedirector>(Object))
{
if(Redirector->DestinationObject == GraphToRemove)
{
Redirector->DestinationObject = nullptr;
}
}
}
}
for (FBPInterfaceDescription& CurrInterface : Blueprint->ImplementedInterfaces)
{
CurrInterface.Graphs.Remove( GraphToRemove );
}
}
else if (UEdGraph* OuterGraph = Cast<UEdGraph>(TestOuter))
{
// remove ourselves
OuterGraph->Modify();
OuterGraph->SubGraphs.Remove(GraphToRemove);
}
else if (! (Cast<UEdGraphNode>(TestOuter) && Cast<UEdGraphNode>(TestOuter)->GetSubGraphs().Num() > 0) )
{
break;
}
}
// Remove timelines held in the graph
TArray<UK2Node_Timeline*> AllTimelineNodes;
GraphToRemove->GetNodesOfClass<UK2Node_Timeline>(AllTimelineNodes);
for (UK2Node_Timeline* TimelineNode : AllTimelineNodes)
{
TimelineNode->DestroyNode();
}
// Handle subgraphs held in graph
TArray<UEdGraphNode*> AllNodes;
GraphToRemove->GetNodesOfClass<UEdGraphNode>(AllNodes);
for (UEdGraphNode* GraphNode : AllNodes)
{
for(UEdGraph* SubGraph : GraphNode->GetSubGraphs())
{
if (SubGraph && SubGraph->GetOuter()->IsA(UEdGraphNode::StaticClass()))
{
FBlueprintEditorUtils::RemoveGraph(Blueprint, SubGraph, EGraphRemoveFlags::None);
}
}
}
GraphToRemove->GetSchema()->HandleGraphBeingDeleted(*GraphToRemove);
GraphToRemove->Rename(nullptr, Blueprint ? Blueprint->GetOuter() : nullptr, REN_DoNotDirty | REN_DontCreateRedirectors);
GraphToRemove->ClearFlags(RF_Standalone | RF_Public);
GraphToRemove->RemoveFromRoot();
if (Flags & EGraphRemoveFlags::MarkTransient)
{
GraphToRemove->SetFlags(RF_Transient);
}
if (Flags & EGraphRemoveFlags::Recompile)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
/** Rename a graph and mark objects for modified */
void FBlueprintEditorUtils::RenameGraph(UEdGraph* Graph, const FString& NewNameStr)
{
if (Graph)
{
// Cache old name
const FName OldGraphName = Graph->GetFName();
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph);
// When renaming a graph inside a macro library, we may have dropped a redirector after a previous
// rename (see below). If we're now trying to reuse it, move the redirector aside to free up the name.
if (Blueprint->BlueprintType == BPTYPE_MacroLibrary)
{
if (UObjectRedirector* Redirector = FindObjectFast<UObjectRedirector>(Graph->GetOuter(), *NewNameStr))
{
Redirector->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors);
}
}
// Ensure that there are no collisions; leave the name as-is if this fails for some reason.
if (!Graph->Rename(*NewNameStr, Graph->GetOuter(), REN_Test))
{
return;
}
auto RenameGraphLambda = [](UEdGraph* GraphToRename, const FName LocalOldGraphName, const FName LocalNewGraphName, ERenameFlags RenameFlags)
{
// Ensure we have undo records
GraphToRename->Modify();
GraphToRename->Rename(*LocalNewGraphName.ToString(), GraphToRename->GetOuter(), RenameFlags);
// Clean function entry & result nodes if they exist
for (UEdGraphNode* Node : GraphToRename->Nodes)
{
if (UK2Node_FunctionEntry* EntryNode = Cast<UK2Node_FunctionEntry>(Node))
{
if (EntryNode->FunctionReference.GetMemberName() == LocalOldGraphName)
{
EntryNode->Modify();
EntryNode->FunctionReference.SetMemberName(LocalNewGraphName);
}
else if (EntryNode->CustomGeneratedFunctionName == LocalOldGraphName)
{
EntryNode->Modify();
EntryNode->CustomGeneratedFunctionName = LocalNewGraphName;
}
}
else if (UK2Node_FunctionResult* ResultNode = Cast<UK2Node_FunctionResult>(Node))
{
if (ResultNode->FunctionReference.GetMemberName() == LocalOldGraphName)
{
ResultNode->Modify();
ResultNode->FunctionReference.SetMemberName(LocalNewGraphName);
}
}
}
};
ERenameFlags RenameFlagsToApply = REN_None;
// Macro library graphs are referenced indirectly and resolved at edit/compile time via GUID (see FGraphReference).
// However, they will be exported by name at save time, so renaming a macro library graph implies we should also
// export a redirector with the old name so that the linker will still be able to resolve existing imports on load.
if (Blueprint->BlueprintType != BPTYPE_MacroLibrary)
{
RenameFlagsToApply |= REN_DontCreateRedirectors;
}
// Apply new name
const FName NewGraphName(*NewNameStr);
RenameGraphLambda(Graph, OldGraphName, NewGraphName, RenameFlagsToApply);
TArray<UBlueprint*> ModifiedBlueprints;
ModifiedBlueprints.Add(Blueprint);
auto PostValidateChildBlueprintLambda = [Blueprint, &OldGraphName, &NewGraphName, &RenameGraphLambda, &ModifiedBlueprints](UBlueprint* InChildBP, const FName /* InVariableName */, bool /* bValidatedVariable */)
{
check(InChildBP);
// Rename child blueprint override graphs.
for (UEdGraph* FunctionGraph : InChildBP->FunctionGraphs)
{
if (FunctionGraph->GetFName() == OldGraphName)
{
RenameGraphLambda(FunctionGraph, OldGraphName, NewGraphName, REN_DontCreateRedirectors);
}
}
if (InChildBP->GetOutermost()->IsDirty())
{
ModifiedBlueprints.Add(InChildBP);
}
};
// Potentially adjust variable names for any child blueprints.
// Note: This will find ALL children (including nested children) so there's no need to do this recursively.
ValidateBlueprintChildVariables(Blueprint, Graph->GetFName(), PostValidateChildBlueprintLambda);
// if it's index is >= 0 we know it was found in the array of functiongraphs
const bool bGraphIsFunction = (Blueprint->FunctionGraphs.IndexOfByKey(Graph) > -1);
// For any nodes that reference a local variable, update the variable's scope to be the graph's new name (which will mirror the UFunction).
if (bGraphIsFunction)
{
// Find all variable nodes in this graph.
TArray<UK2Node_Variable*> VariableNodes;
Graph->GetNodesOfClass<UK2Node_Variable>(VariableNodes);
GetAllChildGraphVariables(Graph, VariableNodes);
for (UK2Node_Variable* const VariableNode : VariableNodes)
{
if (VariableNode->VariableReference.IsLocalScope())
{
// if the rename is the function set the local variable scope to the new name otherwise we leave it with the same scope (Ex: subgraphs in a function)
VariableNode->VariableReference.SetLocalMember(VariableNode->VariableReference.GetMemberName(), NewNameStr, VariableNode->VariableReference.GetMemberGuid());
}
}
}
// Rename any function call points (including any calls w/ a child Blueprint as the target).
for (TObjectIterator<UK2Node_CallFunction> It(RF_ClassDefaultObject | RF_Transient); It; ++It)
{
UK2Node_CallFunction* FunctionNode = *It;
if (FunctionNode && Cast<UEdGraph>(FunctionNode->GetOuter()))
{
if (const UBlueprint* FunctionNodeBlueprint = FBlueprintEditorUtils::FindBlueprintForNode(FunctionNode))
{
if (FunctionNode->FunctionReference.GetMemberName() == OldGraphName)
{
if (FunctionNode->FunctionReference.IsSelfContext() && ModifiedBlueprints.Contains(FunctionNodeBlueprint))
{
FunctionNode->Modify();
FunctionNode->FunctionReference.SetSelfMember(NewGraphName);
}
else if (const UBlueprint* TargetBlueprint = UBlueprint::GetBlueprintFromClass(FunctionNode->FunctionReference.GetMemberParentClass()))
{
if (ModifiedBlueprints.Contains(TargetBlueprint))
{
FunctionNode->Modify();
FunctionNode->FunctionReference.SetExternalMember(NewGraphName, TargetBlueprint->GeneratedClass);
}
}
}
}
}
}
// Replace any other nodes that reference this function
ReplaceFunctionReferences(Blueprint, OldGraphName, NewGraphName);
// We should let the blueprint know we renamed a graph, some stuff may need to be fixed up.
Blueprint->NotifyGraphRenamed(Graph, OldGraphName, NewGraphName);
if (!Blueprint->bIsRegeneratingOnLoad && !Blueprint->bBeingCompiled)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
}
void FBlueprintEditorUtils::RenameGraphWithSuggestion(class UEdGraph* Graph, TSharedPtr<class INameValidatorInterface> NameValidator, const FString& DesiredName )
{
FString NewName = DesiredName;
NameValidator->FindValidString(NewName);
UBlueprint* BP = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph);
Graph->Rename(*NewName, Graph->GetOuter(), REN_DontCreateRedirectors);
}
/**
* Cleans up a Node in the blueprint
*/
void FBlueprintEditorUtils::RemoveNode(UBlueprint* Blueprint, UEdGraphNode* Node, bool bDontRecompile)
{
check(Node);
const UEdGraphSchema* Schema = nullptr;
// Ensure we mark parent graph modified
if (UEdGraph* GraphObj = Node->GetGraph())
{
GraphObj->Modify();
Schema = GraphObj->GetSchema();
}
if (Blueprint != nullptr)
{
// Remove any breakpoints set on the node
FKismetDebugUtilities::RemoveBreakpointFromNode(Node, Blueprint);
// Remove any watches set on the node's pins
for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
{
FKismetDebugUtilities::RemovePinWatch(Blueprint, Node->Pins[PinIndex]);
}
}
Node->Modify();
// Timelines will be removed from the blueprint if the node is a UK2Node_Timeline
if (Schema)
{
Schema->BreakNodeLinks(*Node);
}
Node->DestroyNode();
if (!bDontRecompile && (Blueprint != nullptr))
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
UEdGraph* FBlueprintEditorUtils::GetTopLevelGraph(const UEdGraph* InGraph)
{
UEdGraph* GraphToTest = const_cast<UEdGraph*>(InGraph);
for (UObject* TestOuter = GraphToTest; TestOuter; TestOuter = TestOuter->GetOuter())
{
// reached up to the blueprint for the graph
if (UBlueprint* Blueprint = Cast<UBlueprint>(TestOuter))
{
break;
}
else
{
GraphToTest = Cast<UEdGraph>(TestOuter);
}
}
return GraphToTest;
}
bool FBlueprintEditorUtils::IsGraphReadOnly(UEdGraph* InGraph)
{
bool bGraphReadOnly = true;
if (InGraph)
{
bGraphReadOnly = !InGraph->bEditable;
if (!bGraphReadOnly)
{
const UBlueprint* BlueprintForGraph = FBlueprintEditorUtils::FindBlueprintForGraph(InGraph);
bool const bIsInterface = ((BlueprintForGraph != nullptr) && (BlueprintForGraph->BlueprintType == BPTYPE_Interface));
bool const bIsDelegate = FBlueprintEditorUtils::IsDelegateSignatureGraph(InGraph);
bool const bIsMathExpression = FBlueprintEditorUtils::IsMathExpressionGraph(InGraph);
bGraphReadOnly = bIsInterface || bIsDelegate || bIsMathExpression;
}
}
return bGraphReadOnly;
}
UK2Node_Event* FBlueprintEditorUtils::FindOverrideForFunction(const UBlueprint* Blueprint, const UClass* SignatureClass, FName SignatureName)
{
TArray<UK2Node_Event*> AllEvents;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Event>(Blueprint, AllEvents);
for(int32 i=0; i<AllEvents.Num(); i++)
{
UK2Node_Event* EventNode = AllEvents[i];
check(EventNode);
if( EventNode->bOverrideFunction == true &&
EventNode->EventReference.GetMemberName() == SignatureName )
{
const UClass* MemberParentClass = EventNode->EventReference.GetMemberParentClass(EventNode->GetBlueprintClassFromNode());
if(MemberParentClass && MemberParentClass->IsChildOf(SignatureClass))
{
return EventNode;
}
}
}
return nullptr;
}
UK2Node_Event* FBlueprintEditorUtils::FindCustomEventNode(const UBlueprint* Blueprint, FName const CustomName)
{
UK2Node_Event* FoundNode = nullptr;
if (CustomName != NAME_None)
{
TArray<UK2Node_Event*> AllEvents;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Event>(Blueprint, AllEvents);
for (UK2Node_Event* EventNode : AllEvents)
{
if (EventNode->CustomFunctionName == CustomName)
{
FoundNode = EventNode;
break;
}
}
}
return FoundNode;
}
void FBlueprintEditorUtils::GatherDependencies(const UBlueprint* InBlueprint, TSet<TWeakObjectPtr<UBlueprint>>& Dependencies, TSet<TWeakObjectPtr<UStruct>>& OutUDSDependencies)
{
struct FGatherDependenciesHelper
{
static UBlueprint* GetGeneratingBlueprint(const UObject* Obj)
{
const UBlueprintGeneratedClass* BPGC = nullptr;
while (!BPGC && Obj)
{
BPGC = Cast<const UBlueprintGeneratedClass>(Obj);
Obj = Obj->GetOuter();
}
return UBlueprint::GetBlueprintFromClass(BPGC);
}
static void ProcessHierarchy(const UStruct* Struct, TSet<TWeakObjectPtr<UBlueprint>>& InDependencies)
{
for (UBlueprint* Blueprint = GetGeneratingBlueprint(Struct);
Blueprint;
Blueprint = UBlueprint::GetBlueprintFromClass(Cast<UBlueprintGeneratedClass>(Blueprint->ParentClass)))
{
bool bAlreadyProcessed = false;
InDependencies.Add(Blueprint, &bAlreadyProcessed);
if (bAlreadyProcessed)
{
return;
}
Blueprint->GatherDependencies(InDependencies);
}
}
};
check(InBlueprint);
Dependencies.Empty();
OutUDSDependencies.Empty();
// If the Blueprint's GeneratedClass was not generated by the Blueprint, it's either corrupt or a PIE version of the BP
if (InBlueprint->GeneratedClass && InBlueprint->GeneratedClass->ClassGeneratedBy.Get() != InBlueprint)
{
// Dependencies do not matter for PIE duplicated Blueprints
return;
}
InBlueprint->GatherDependencies(Dependencies);
FGatherDependenciesHelper::ProcessHierarchy(InBlueprint->ParentClass, Dependencies);
for (const FBPInterfaceDescription& InterfaceDesc : InBlueprint->ImplementedInterfaces)
{
UBlueprint* InterfaceBP = InterfaceDesc.Interface ? Cast<UBlueprint>(InterfaceDesc.Interface->ClassGeneratedBy) : nullptr;
if (InterfaceBP)
{
Dependencies.Add(InterfaceBP);
}
}
TArray<UEdGraph*> Graphs;
InBlueprint->GetAllGraphs(Graphs);
for (UEdGraph* Graph : Graphs)
{
if (Graph && !FBlueprintEditorUtils::IsGraphIntermediate(Graph))
{
TArray<UK2Node*> Nodes;
Graph->GetNodesOfClass(Nodes);
for (UK2Node* Node : Nodes)
{
TArray<UStruct*> LocalDependentStructures;
if (Node && Node->HasExternalDependencies(&LocalDependentStructures))
{
for (UStruct* Struct : LocalDependentStructures)
{
if (UUserDefinedStruct* UDS = Cast<UUserDefinedStruct>(Struct))
{
OutUDSDependencies.Add(UDS);
}
else
{
FGatherDependenciesHelper::ProcessHierarchy(Struct, Dependencies);
}
}
}
}
}
}
Dependencies.Remove(MakeWeakObjectPtr(const_cast<UBlueprint*>(InBlueprint)));
}
void FBlueprintEditorUtils::EnsureCachedDependenciesUpToDate(UBlueprint* Blueprint)
{
if (Blueprint && !Blueprint->bCachedDependenciesUpToDate)
{
GatherDependencies(Blueprint, Blueprint->CachedDependencies, Blueprint->CachedUDSDependencies);
Blueprint->bCachedDependenciesUpToDate = true;
// A macro dependency will result in an expansion from an external graph rather than a local one, so we must also include its dependencies.
TSet<TWeakObjectPtr<UBlueprint>> LocalCopyOfCachedDependencies = Blueprint->CachedDependencies;
for (const TWeakObjectPtr<UBlueprint>& Dependency : LocalCopyOfCachedDependencies)
{
UBlueprint* ResolvedDependency = Dependency.Get();
if (ResolvedDependency && ResolvedDependency->BlueprintType == BPTYPE_MacroLibrary)
{
EnsureCachedDependenciesUpToDate(ResolvedDependency);
Blueprint->CachedDependencies.Append(ResolvedDependency->CachedDependencies);
Blueprint->CachedUDSDependencies.Append(ResolvedDependency->CachedUDSDependencies);
}
}
}
}
void FBlueprintEditorUtils::GetDependentBlueprints(UBlueprint* Blueprint, TArray<UBlueprint*>& DependentBlueprints)
{
for( TWeakObjectPtr<UBlueprint> DependentBPWeak : Blueprint->CachedDependents )
{
if(UBlueprint* DependentBP = DependentBPWeak.Get())
{
DependentBlueprints.Add(DependentBP);
}
}
}
void FBlueprintEditorUtils::FindDependentBlueprints(UBlueprint* Blueprint, TArray<UBlueprint*>& DependentBlueprints)
{
if(Blueprint == nullptr)
{
return;
}
TArray<UObject*> AllBlueprints;
bool const bIncludeDerivedClasses = true;
GetObjectsOfClass(UBlueprint::StaticClass(), AllBlueprints, bIncludeDerivedClasses );
// Sanitize, add correct type.. can't find a UObject* helper to do this, and
// the previous version of htis code checked IsPendingKill():
TArray<UBlueprint*> AllBlueprintSafe;
Algo::TransformIf(AllBlueprints, AllBlueprintSafe,
[](UObject* Obj)->bool { return IsValid(Obj); },
[](UObject* Obj)->UBlueprint* { return static_cast<UBlueprint*>(Obj); }
);
// Update *all* dependencies:
for (UBlueprint* TestBP : AllBlueprintSafe)
{
EnsureCachedDependenciesUpToDate(TestBP);
}
// Gather macro blueprints that we're dependent on:
TSet<UBlueprint*> DepSet;
Algo::CopyIf(AllBlueprintSafe, DepSet,
[Blueprint](UBlueprint* TestBP) -> bool
{
if(TestBP->CachedDependencies.Contains(Blueprint))
{
if (TestBP->BlueprintType == BPTYPE_MacroLibrary)
{
return true;
}
}
return false;
}
);
DepSet.Add(Blueprint);
// Find all blueprints that have a cached dep on Blueprint *or* one of the
// macros that is dependent on Blueprint:
Algo::CopyIf(AllBlueprintSafe, DependentBlueprints,
[DepSet](UBlueprint* TestBP) -> bool
{
// check for interesection between CachedDependencies and DepSet:
for(TWeakObjectPtr<UBlueprint> ObjWeak : TestBP->CachedDependencies)
{
if(DepSet.Contains(ObjWeak.Get()))
{
return true;
}
}
return false;
}
);
// Remove ourself from the list of dependents:
DependentBlueprints.RemoveSwap(Blueprint);
}
bool FBlueprintEditorUtils::IsGraphIntermediate(const UEdGraph* Graph)
{
if (Graph)
{
return Graph->HasAllFlags(RF_Transient);
}
return false;
}
bool FBlueprintEditorUtils::IsDataOnlyBlueprint(const UBlueprint* Blueprint)
{
// Blueprint interfaces are always compiled
if (Blueprint->BlueprintType == BPTYPE_Interface)
{
return false;
}
if (Blueprint->AlwaysCompileOnLoad())
{
return false;
}
// No new variables defined
if (Blueprint->NewVariables.Num() > 0)
{
return false;
}
// No extra functions, other than the user construction script(only AActor and subclasses of AActor have)
const int32 DefaultFunctionNum = (Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(AActor::StaticClass())) ? 1 : 0;
if ((Blueprint->FunctionGraphs.Num() > DefaultFunctionNum) || (Blueprint->MacroGraphs.Num() > 0))
{
return false;
}
if (Blueprint->DelegateSignatureGraphs.Num())
{
return false;
}
if (Blueprint->ComponentTemplates.Num() > 0 || Blueprint->Timelines.Num() > 0 || (Blueprint->ComponentClassOverrides.Num() > 0 && GetAllowNativeComponentClassOverrides()))
{
return false;
}
if (USimpleConstructionScript* SimpleConstructionScript = Blueprint->SimpleConstructionScript)
{
const TArray<USCS_Node*>& Nodes = SimpleConstructionScript->GetAllNodes();
if (Nodes.Num() > 1)
{
return false;
}
if ((1 == Nodes.Num()) && (Nodes[0] != SimpleConstructionScript->GetDefaultSceneRootNode()))
{
return false;
}
}
// Make sure there's nothing in the user construction script, other than an entry node
UEdGraph* UserConstructionScript = (Blueprint->FunctionGraphs.Num() == 1) ? ToRawPtr(Blueprint->FunctionGraphs[0]) : nullptr;
if (UserConstructionScript && Blueprint->ParentClass)
{
//Call parent construction script may be added automatically
UBlueprint* BlueprintParent = Cast<UBlueprint>(Blueprint->ParentClass->ClassGeneratedBy);
// just 1 entry node or just one entry node and a call to our super, which is DataOnly:
if ( !BlueprintParent && UserConstructionScript->Nodes.Num() > 1 )
{
return false;
}
else if (BlueprintParent)
{
// More than two nodes.. one of them must do something (same logic as above, but we have a call to super as well)
if (UserConstructionScript->Nodes.Num() > 2)
{
return false;
}
else
{
// Just make sure the nodes are trivial, if they aren't then we're not data only:
for (UEdGraphNode* Node : UserConstructionScript->Nodes)
{
if (!Cast<UK2Node_FunctionEntry>(Node) &&
!Cast<UK2Node_CallParentFunction>(Node))
{
return false;
}
}
}
}
}
// All EventGraphs are empty (at least of non-ghost, non-disabled nodes)
for (UEdGraph* EventGraph : Blueprint->UbergraphPages)
{
for (UEdGraphNode* GraphNode : EventGraph->Nodes)
{
// If there is an enabled node in the event graph, the Blueprint is not data only
if (GraphNode && (GraphNode->GetDesiredEnabledState() != ENodeEnabledState::Disabled))
{
return false;
}
}
}
// No implemented interfaces
if (Blueprint->ImplementedInterfaces.Num() > 0)
{
return false;
}
return true;
}
bool FBlueprintEditorUtils::IsBlueprintConst(const UBlueprint* Blueprint)
{
// Macros aren't marked as const because they can modify variables when instanced into a non const class
// and will be caught at compile time if they're modifying variables on a const class.
return Blueprint && Blueprint->BlueprintType == BPTYPE_Const;
}
bool FBlueprintEditorUtils::IsEditorUtilityBlueprint(const UBlueprint* Blueprint)
{
IBlutilityModule* BlutilityModule = FModuleManager::GetModulePtr<IBlutilityModule>("Blutility");
if (BlutilityModule)
{
return BlutilityModule->IsEditorUtilityBlueprint( Blueprint );
}
return false;
}
bool FBlueprintEditorUtils::IsActorBased(const UBlueprint* Blueprint)
{
return Blueprint && Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(AActor::StaticClass());
}
bool FBlueprintEditorUtils::IsComponentBased(const UBlueprint* Blueprint)
{
return Blueprint && Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf(UActorComponent::StaticClass());
}
bool FBlueprintEditorUtils::IsDelegateSignatureGraph(const UEdGraph* Graph)
{
if(Graph)
{
if(const UBlueprint* Blueprint = FindBlueprintForGraph(Graph))
{
return (nullptr != Blueprint->DelegateSignatureGraphs.FindByKey(Graph));
}
}
return false;
}
bool FBlueprintEditorUtils::IsMathExpressionGraph(const UEdGraph* InGraph)
{
if(InGraph)
{
return InGraph->GetOuter()->GetClass() == UK2Node_MathExpression::StaticClass();
}
return false;
}
bool FBlueprintEditorUtils::IsInterfaceBlueprint(const UBlueprint* Blueprint)
{
return (Blueprint && Blueprint->BlueprintType == BPTYPE_Interface);
}
bool FBlueprintEditorUtils::IsInterfaceGraph(const UEdGraph* Graph)
{
return IsInterfaceBlueprint(FindBlueprintForGraph(Graph));
}
bool FBlueprintEditorUtils::IsLevelScriptBlueprint(const UBlueprint* Blueprint)
{
return (Blueprint && Blueprint->BlueprintType == BPTYPE_LevelScript);
}
bool FBlueprintEditorUtils::IsParentClassABlueprint(const UBlueprint* Blueprint)
{
if (Blueprint)
{
UObject* ParentClass = Blueprint->ParentClass;
if (ParentClass)
{
if (ParentClass->IsA(UBlueprintGeneratedClass::StaticClass()))
{
return true;
}
}
}
return false;
}
bool FBlueprintEditorUtils::IsParentClassAnEditableBlueprint(const UBlueprint* Blueprint)
{
if (Blueprint)
{
UObject* ParentClass = Blueprint->ParentClass;
if (ParentClass)
{
UBlueprintGeneratedClass* ParentBPGC = Cast<UBlueprintGeneratedClass>(ParentClass);
if (ParentBPGC && ParentBPGC->ClassGeneratedBy)
{
return true;
}
}
}
return false;
}
bool FBlueprintEditorUtils::IsAnonymousBlueprintClass(const UClass* Class)
{
return (Class && Class->GetOutermost()->ContainsMap());
}
ULevel* FBlueprintEditorUtils::GetLevelFromBlueprint(const UBlueprint* Blueprint)
{
return (Blueprint ? Cast<ULevel>(Blueprint->GetOuter()) : nullptr);
}
bool FBlueprintEditorUtils::SupportsConstructionScript(const UBlueprint* Blueprint)
{
return( !FBlueprintEditorUtils::IsInterfaceBlueprint(Blueprint) &&
!FBlueprintEditorUtils::IsBlueprintConst(Blueprint) &&
!FBlueprintEditorUtils::IsLevelScriptBlueprint(Blueprint) &&
FBlueprintEditorUtils::IsActorBased(Blueprint)) &&
!(Blueprint->BlueprintType == BPTYPE_MacroLibrary) &&
!(Blueprint->BlueprintType == BPTYPE_FunctionLibrary);
}
bool FBlueprintEditorUtils::CanClassGenerateEvents(const UClass* InClass)
{
if( InClass )
{
for( TFieldIterator<FMulticastDelegateProperty> PropertyIt( InClass, EFieldIteratorFlags::IncludeSuper ); PropertyIt; ++PropertyIt )
{
FProperty* Property = *PropertyIt;
if( !Property->HasAnyPropertyFlags( CPF_Parm ) && Property->HasAllPropertyFlags( CPF_BlueprintAssignable ))
{
return true;
}
}
}
return false;
}
bool FBlueprintEditorUtils::CanCreateChildBlueprint(const UBlueprint* BP)
{
if (!BP)
{
return false;
}
// BP function libraries cannot have child BP's created of them. You will only ever get compilation
// errors if you made one.
if (BP->BlueprintType == EBlueprintType::BPTYPE_FunctionLibrary)
{
return false;
}
// Do not allow child classes to be created from deprecated BPs
return BP->GeneratedClass && !BP->GeneratedClass->HasAnyClassFlags(CLASS_Deprecated);
}
UEdGraph* FBlueprintEditorUtils::FindUserConstructionScript(const UBlueprint* Blueprint)
{
for (UEdGraph* CurrentGraph : Blueprint->FunctionGraphs)
{
if( CurrentGraph->GetFName() == UEdGraphSchema_K2::FN_UserConstructionScript )
{
return CurrentGraph;
}
}
return nullptr;
}
UEdGraph* FBlueprintEditorUtils::FindEventGraph(const UBlueprint* Blueprint)
{
for (UEdGraph* CurrentGraph : Blueprint->UbergraphPages)
{
if( CurrentGraph->GetFName() == UEdGraphSchema_K2::GN_EventGraph )
{
return CurrentGraph;
}
}
return nullptr;
}
bool FBlueprintEditorUtils::IsEventGraph(const UEdGraph* InGraph)
{
if (InGraph)
{
if (const UBlueprint* Blueprint = FindBlueprintForGraph(InGraph))
{
return (nullptr != Blueprint->UbergraphPages.FindByKey(InGraph));
}
}
return false;
}
bool FBlueprintEditorUtils::IsTunnelInstanceNode(const UEdGraphNode* InGraphNode)
{
if (InGraphNode)
{
return InGraphNode->IsA<UK2Node_MacroInstance>() || InGraphNode->IsA<UK2Node_Composite>();
}
return false;
}
bool FBlueprintEditorUtils::DoesBlueprintDeriveFrom(const UBlueprint* Blueprint, UClass* TestClass)
{
check(Blueprint->SkeletonGeneratedClass != nullptr);
return TestClass != nullptr &&
Blueprint->SkeletonGeneratedClass->IsChildOf(TestClass);
}
bool FBlueprintEditorUtils::DoesBlueprintContainField(const UBlueprint* Blueprint, UField* TestField)
{
// Get the class of the field
if(TestField)
{
// Local properties do not have a UClass outer but are also not a part of the Blueprint
UClass* TestClass = Cast<UClass>(TestField->GetOuter());
if(TestClass)
{
return FBlueprintEditorUtils::DoesBlueprintDeriveFrom(Blueprint, TestClass);
}
}
return false;
}
bool FBlueprintEditorUtils::DoesSupportOverridingFunctions(const UBlueprint* Blueprint)
{
return Blueprint->BlueprintType != BPTYPE_MacroLibrary
&& Blueprint->BlueprintType != BPTYPE_Interface
&& Blueprint->BlueprintType != BPTYPE_FunctionLibrary;
}
bool FBlueprintEditorUtils::DoesSupportTimelines(const UBlueprint* Blueprint)
{
// Right now, just assume actor based blueprints support timelines
return FBlueprintEditorUtils::IsActorBased(Blueprint) && FBlueprintEditorUtils::DoesSupportEventGraphs(Blueprint);
}
bool FBlueprintEditorUtils::DoesSupportEventGraphs(const UBlueprint* Blueprint)
{
return Blueprint->BlueprintType == BPTYPE_Normal
|| Blueprint->BlueprintType == BPTYPE_LevelScript;
}
/** Returns whether or not the blueprint supports implementing interfaces */
bool FBlueprintEditorUtils::DoesSupportImplementingInterfaces(const UBlueprint* Blueprint)
{
return Blueprint->BlueprintType != BPTYPE_MacroLibrary
&& Blueprint->BlueprintType != BPTYPE_Interface
&& Blueprint->BlueprintType != BPTYPE_LevelScript
&& Blueprint->BlueprintType != BPTYPE_FunctionLibrary;
}
bool FBlueprintEditorUtils::DoesSupportComponents(UBlueprint const* Blueprint)
{
return (Blueprint->SimpleConstructionScript != nullptr) // An SCS must be present (otherwise there is nothing valid to edit)
&& FBlueprintEditorUtils::IsActorBased(Blueprint) // Must be parented to an AActor-derived class (some older BPs may have an SCS but may not be Actor-based)
&& (Blueprint->BlueprintType != BPTYPE_MacroLibrary) // Must not be a macro-type Blueprint
&& (Blueprint->BlueprintType != BPTYPE_FunctionLibrary); // Must not be a function library
}
bool FBlueprintEditorUtils::DoesSupportDefaults(UBlueprint const* Blueprint)
{
return Blueprint->BlueprintType != BPTYPE_MacroLibrary
&& Blueprint->BlueprintType != BPTYPE_FunctionLibrary;
}
bool FBlueprintEditorUtils::DoesSupportLocalVariables(UEdGraph const* InGraph)
{
if(InGraph)
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(InGraph);
return Blueprint
&& Blueprint->BlueprintType != BPTYPE_Interface
&& InGraph->GetSchema()->GetGraphType(InGraph) == EGraphType::GT_Function
&& !InGraph->IsA(UAnimationTransitionGraph::StaticClass());
}
return false;
}
// Returns a descriptive name of the type of blueprint passed in
FString FBlueprintEditorUtils::GetBlueprintTypeDescription(const UBlueprint* Blueprint)
{
FString BlueprintTypeString;
switch (Blueprint->BlueprintType)
{
case BPTYPE_LevelScript:
BlueprintTypeString = LOCTEXT("BlueprintType_LevelScript", "Level Blueprint").ToString();
break;
case BPTYPE_MacroLibrary:
BlueprintTypeString = LOCTEXT("BlueprintType_MacroLibrary", "Macro Library").ToString();
break;
case BPTYPE_Interface:
BlueprintTypeString = LOCTEXT("BlueprintType_Interface", "Interface").ToString();
break;
case BPTYPE_FunctionLibrary:
case BPTYPE_Normal:
case BPTYPE_Const:
BlueprintTypeString = Blueprint->GetClass()->GetName();
break;
default:
BlueprintTypeString = TEXT("Unknown blueprint type");
}
return BlueprintTypeString;
}
//////////////////////////////////////////////////////////////////////////
// Variables
bool FBlueprintEditorUtils::IsVariableCreatedByBlueprint(UBlueprint* InBlueprint, FProperty* InVariableProperty)
{
bool bIsVariableCreatedByBlueprint = false;
if (UBlueprintGeneratedClass* GeneratedClass = Cast<UBlueprintGeneratedClass>(InVariableProperty->GetOwnerClass()))
{
UBlueprint* OwnerBlueprint = Cast<UBlueprint>(GeneratedClass->ClassGeneratedBy);
bIsVariableCreatedByBlueprint = (OwnerBlueprint == InBlueprint && FBlueprintEditorUtils::FindNewVariableIndex(InBlueprint, InVariableProperty->GetFName()) != INDEX_NONE);
}
return bIsVariableCreatedByBlueprint;
}
// Find the index of a variable first declared in this blueprint. Returns INDEX_NONE if not found.
int32 FBlueprintEditorUtils::FindNewVariableIndex(const UBlueprint* Blueprint, const FName& InName)
{
if(InName != NAME_None)
{
for(int32 i=0; i<Blueprint->NewVariables.Num(); i++)
{
if(Blueprint->NewVariables[i].VarName == InName)
{
return i;
}
}
}
return INDEX_NONE;
}
int32 FBlueprintEditorUtils::FindNewVariableIndexAndBlueprint(UBlueprint* InBlueprint, FName InName, UBlueprint*& OutFoundBlueprint)
{
OutFoundBlueprint = InBlueprint;
while (OutFoundBlueprint)
{
int32 FoundIndex = FindNewVariableIndex(OutFoundBlueprint, InName);
if (FoundIndex != INDEX_NONE)
{
return FoundIndex;
}
else
{
OutFoundBlueprint = UBlueprint::GetBlueprintFromClass(OutFoundBlueprint->ParentClass);
}
}
return INDEX_NONE;
}
int32 FBlueprintEditorUtils::FindLocalVariableIndex(const UBlueprint* Blueprint, UStruct* VariableScope, const FName& InVariableName)
{
UK2Node_FunctionEntry* FunctionEntryNode = nullptr;
FindLocalVariable(Blueprint, VariableScope, InVariableName, &FunctionEntryNode);
if (FunctionEntryNode != nullptr)
{
for (int32 i = 0; i < FunctionEntryNode->LocalVariables.Num(); i++)
{
if (FunctionEntryNode->LocalVariables[i].VarName == InVariableName)
{
return i;
}
}
}
return INDEX_NONE;
}
/** Helper function for moving variables around in the list relative to a target variable */
static bool MoveVariableRelativeToVariable(UBlueprint* Blueprint, UStruct* VariableScope, FName VarNameToMove, FName TargetVarName, bool bDontRecompile, bool bInsertBeforeTargetVariable)
{
check(Blueprint && VariableScope);
bool bMoved = false;
int32 VarIndexToMove = INDEX_NONE;
int32 TargetVarIndex = INDEX_NONE;
//Get the indices of the variables to be re-ordered
if (VariableScope->IsA(UFunction::StaticClass()))
{
VarIndexToMove = FBlueprintEditorUtils::FindLocalVariableIndex(Blueprint, VariableScope, VarNameToMove);
TargetVarIndex = FBlueprintEditorUtils::FindLocalVariableIndex(Blueprint, VariableScope, TargetVarName);
}
else
{
VarIndexToMove = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarNameToMove);
TargetVarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, TargetVarName);
}
if (VarIndexToMove != INDEX_NONE && TargetVarIndex != INDEX_NONE)
{
// When we remove item, will back all items after it. If your target is after it, need to adjust
if (TargetVarIndex > VarIndexToMove)
{
TargetVarIndex--;
}
//Handle Local vs Class scope
if (VariableScope->IsA(UFunction::StaticClass()))
{
UK2Node_FunctionEntry* FunctionEntryNode = nullptr;
FBlueprintEditorUtils::FindLocalVariable(Blueprint, VariableScope, VarNameToMove, &FunctionEntryNode);
if (FunctionEntryNode != nullptr)
{
// Copy var we want to move
FBPVariableDescription MoveVar = FunctionEntryNode->LocalVariables[VarIndexToMove];
// Remove var we are moving
FunctionEntryNode->LocalVariables.RemoveAt(VarIndexToMove);
// Add in before target variable
FunctionEntryNode->LocalVariables.Insert(MoveVar, bInsertBeforeTargetVariable ? TargetVarIndex : TargetVarIndex + 1);
}
}
else
{
// Copy var we want to move
FBPVariableDescription MoveVar = Blueprint->NewVariables[VarIndexToMove];
// Remove var we are moving
Blueprint->NewVariables.RemoveAt(VarIndexToMove);
// Add in before target variable
Blueprint->NewVariables.Insert(MoveVar, bInsertBeforeTargetVariable ? TargetVarIndex : TargetVarIndex + 1);
}
if (!bDontRecompile)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
bMoved = true;
}
return bMoved;
}
bool FBlueprintEditorUtils::MoveVariableBeforeVariable(UBlueprint* Blueprint, UStruct* VariableScope, FName VarNameToMove, FName TargetVarName, bool bDontRecompile)
{
return MoveVariableRelativeToVariable(Blueprint, VariableScope, VarNameToMove, TargetVarName, bDontRecompile, true);
}
bool FBlueprintEditorUtils::MoveVariableAfterVariable(UBlueprint* Blueprint, UStruct* VariableScope, FName VarNameToMove, FName TargetVarName, bool bDontRecompile)
{
return MoveVariableRelativeToVariable(Blueprint, VariableScope, VarNameToMove, TargetVarName, bDontRecompile, false);
}
int32 FBlueprintEditorUtils::FindTimelineIndex(const UBlueprint* Blueprint, const FName& InName)
{
const FName TimelineTemplateName = *UTimelineTemplate::TimelineVariableNameToTemplateName(InName);
for(int32 i=0; i<Blueprint->Timelines.Num(); i++)
{
if(Blueprint->Timelines[i] && Blueprint->Timelines[i]->GetFName() == TimelineTemplateName)
{
return i;
}
}
return INDEX_NONE;
}
void FBlueprintEditorUtils::GetSCSVariableNameList(const UBlueprint* Blueprint, TSet<FName>& VariableNames)
{
if (Blueprint)
{
GetSCSVariableNameList(Blueprint->SimpleConstructionScript, VariableNames);
}
}
void FBlueprintEditorUtils::GetSCSVariableNameList(const UBlueprintGeneratedClass* BPGC, TSet<FName>& VariableNames)
{
if (BPGC)
{
GetSCSVariableNameList(BPGC->SimpleConstructionScript, VariableNames);
}
}
void FBlueprintEditorUtils::GetSCSVariableNameList(const USimpleConstructionScript* SCS, TSet<FName>& VariableNames)
{
if (SCS)
{
for (USCS_Node* SCS_Node : SCS->GetAllNodes())
{
if (SCS_Node)
{
const FName VariableName = SCS_Node->GetVariableName();
if (VariableName != NAME_None)
{
VariableNames.Add(VariableName);
}
}
}
}
}
void FBlueprintEditorUtils::GetImplementingBlueprintsFunctionNameList(const UBlueprint* Blueprint, TSet<FName>& FunctionNames)
{
if(Blueprint != nullptr && FBlueprintEditorUtils::IsInterfaceBlueprint(Blueprint))
{
for(TObjectIterator<UBlueprint> BlueprintIt; BlueprintIt; ++BlueprintIt)
{
const UBlueprint* ChildBlueprint = *BlueprintIt;
if(ChildBlueprint != nullptr)
{
for (int32 InterfaceIndex = 0; InterfaceIndex < ChildBlueprint->ImplementedInterfaces.Num(); InterfaceIndex++)
{
const FBPInterfaceDescription& CurrentInterface = ChildBlueprint->ImplementedInterfaces[InterfaceIndex];
const UBlueprint* BlueprintInterfaceClass = UBlueprint::GetBlueprintFromClass(CurrentInterface.Interface);
if(BlueprintInterfaceClass != nullptr && BlueprintInterfaceClass == Blueprint)
{
FBlueprintEditorUtils::GetAllGraphNames(ChildBlueprint, FunctionNames);
}
}
}
}
}
}
USCS_Node* FBlueprintEditorUtils::FindSCS_Node(const UBlueprint* Blueprint, const FName InName)
{
if (Blueprint->SimpleConstructionScript)
{
const TArray<USCS_Node*>& AllSCS_Nodes = Blueprint->SimpleConstructionScript->GetAllNodes();
for (USCS_Node* SCSNode : AllSCS_Nodes)
{
if (SCSNode->GetVariableName() == InName)
{
return SCSNode;
}
}
}
return nullptr;
}
void FBlueprintEditorUtils::SetBlueprintOnlyEditableFlag(UBlueprint* Blueprint, const FName& VarName, const bool bNewBlueprintOnly)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (bNewBlueprintOnly)
{
FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(Blueprint, VarName, nullptr, FEdMode::MD_MakeEditWidget);
}
if (VarIndex != INDEX_NONE)
{
if( bNewBlueprintOnly )
{
Blueprint->NewVariables[VarIndex].PropertyFlags |= CPF_DisableEditOnInstance;
}
else
{
Blueprint->NewVariables[VarIndex].PropertyFlags &= ~CPF_DisableEditOnInstance;
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
void FBlueprintEditorUtils::SetBlueprintPropertyReadOnlyFlag(UBlueprint* Blueprint, const FName& VarName, const bool bVariableReadOnly)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (bVariableReadOnly)
{
FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(Blueprint, VarName, nullptr, FEdMode::MD_MakeEditWidget);
}
if (VarIndex != INDEX_NONE)
{
if (bVariableReadOnly)
{
Blueprint->NewVariables[VarIndex].PropertyFlags |= CPF_BlueprintReadOnly;
}
else
{
Blueprint->NewVariables[VarIndex].PropertyFlags &= ~CPF_BlueprintReadOnly;
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
void FBlueprintEditorUtils::SetInterpFlag(UBlueprint* Blueprint, const FName& VarName, const bool bInterp)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex != INDEX_NONE)
{
if( bInterp )
{
Blueprint->NewVariables[VarIndex].PropertyFlags |= (CPF_Interp);
}
else
{
Blueprint->NewVariables[VarIndex].PropertyFlags &= ~(CPF_Interp);
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
void FBlueprintEditorUtils::SetVariableTransientFlag(UBlueprint* InBlueprint, const FName& InVarName, const bool bInIsTransient)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(InBlueprint, InVarName);
if (VarIndex != INDEX_NONE)
{
if( bInIsTransient )
{
InBlueprint->NewVariables[VarIndex].PropertyFlags |= CPF_Transient;
}
else
{
InBlueprint->NewVariables[VarIndex].PropertyFlags &= ~CPF_Transient;
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint);
}
void FBlueprintEditorUtils::SetVariableSaveGameFlag(UBlueprint* InBlueprint, const FName& InVarName, const bool bInIsSaveGame)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(InBlueprint, InVarName);
if (VarIndex != INDEX_NONE)
{
if( bInIsSaveGame )
{
InBlueprint->NewVariables[VarIndex].PropertyFlags |= CPF_SaveGame;
}
else
{
InBlueprint->NewVariables[VarIndex].PropertyFlags &= ~CPF_SaveGame;
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint);
}
void FBlueprintEditorUtils::SetVariableAdvancedDisplayFlag(UBlueprint* InBlueprint, const FName& InVarName, const bool bInIsAdvancedDisplay)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(InBlueprint, InVarName);
if (VarIndex != INDEX_NONE)
{
if( bInIsAdvancedDisplay )
{
InBlueprint->NewVariables[VarIndex].PropertyFlags |= CPF_AdvancedDisplay;
}
else
{
InBlueprint->NewVariables[VarIndex].PropertyFlags &= ~CPF_AdvancedDisplay;
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint);
}
void FBlueprintEditorUtils::SetVariableDeprecatedFlag(UBlueprint* InBlueprint, const FName& InVarName, const bool bInIsDeprecated)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(InBlueprint, InVarName);
if (VarIndex != INDEX_NONE)
{
if (bInIsDeprecated)
{
InBlueprint->NewVariables[VarIndex].PropertyFlags |= CPF_Deprecated;
}
else
{
InBlueprint->NewVariables[VarIndex].PropertyFlags &= ~CPF_Deprecated;
}
}
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint);
}
struct FMetaDataDependencyHelper
{
static void OnChange(UBlueprint* Blueprint, FName MetaDataKey)
{
if (Blueprint && (FBlueprintMetadata::MD_ExposeOnSpawn == MetaDataKey))
{
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
for (UEdGraph* Graph : AllGraphs)
{
if (Graph)
{
const UEdGraphSchema* Schema = Graph->GetSchema();
TArray<UK2Node_SpawnActorFromClass*> LocalSpawnNodes;
Graph->GetNodesOfClass(LocalSpawnNodes);
for (UK2Node_SpawnActorFromClass* Node : LocalSpawnNodes)
{
UClass* ClassToSpawn = Node ? Node->GetClassToSpawn() : nullptr;
if (ClassToSpawn && ClassToSpawn->IsChildOf(Blueprint->GeneratedClass))
{
Schema->ReconstructNode(*Node, true);
}
}
}
}
}
}
};
void FBlueprintEditorUtils::SetBlueprintVariableMetaData(UBlueprint* Blueprint, const FName& VarName, const UStruct* InLocalVarScope, const FName& MetaDataKey, const FString& MetaDataValue)
{
// If there is a local var scope, we know we are looking at a local variable
if(InLocalVarScope)
{
if(FBPVariableDescription* LocalVariable = FindLocalVariable(Blueprint, InLocalVarScope, VarName))
{
LocalVariable->SetMetaData(MetaDataKey, *MetaDataValue);
}
}
else
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex == INDEX_NONE)
{
//Not a NewVariable is the VarName from a Timeline?
const int32 TimelineIndex = FBlueprintEditorUtils::FindTimelineIndex(Blueprint,VarName);
if (TimelineIndex == INDEX_NONE)
{
//Not a Timeline is this a SCS Node?
if (USCS_Node* SCSNode = FBlueprintEditorUtils::FindSCS_Node(Blueprint,VarName))
{
SCSNode->SetMetaData(MetaDataKey, MetaDataValue);
}
}
else
{
Blueprint->Timelines[TimelineIndex]->SetMetaData(MetaDataKey, MetaDataValue);
}
}
else
{
Blueprint->NewVariables[VarIndex].SetMetaData(MetaDataKey, MetaDataValue);
FProperty* Property = FindFProperty<FProperty>(Blueprint->SkeletonGeneratedClass, VarName);
if (Property)
{
Property->SetMetaData(MetaDataKey, *MetaDataValue);
}
Property = FindFProperty<FProperty>(Blueprint->GeneratedClass, VarName);
if (Property)
{
Property->SetMetaData(MetaDataKey, *MetaDataValue);
}
}
}
FMetaDataDependencyHelper::OnChange(Blueprint, MetaDataKey);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
bool FBlueprintEditorUtils::GetBlueprintVariableMetaData(const UBlueprint* Blueprint, const FName& VarName, const UStruct* InLocalVarScope, const FName& MetaDataKey, FString& OutMetaDataValue)
{
// If there is a local var scope, we know we are looking at a local variable
if(InLocalVarScope)
{
if(FBPVariableDescription* LocalVariable = FindLocalVariable(Blueprint, InLocalVarScope, VarName))
{
int32 EntryIndex = LocalVariable->FindMetaDataEntryIndexForKey(MetaDataKey);
if (EntryIndex != INDEX_NONE)
{
OutMetaDataValue = LocalVariable->GetMetaData(MetaDataKey);
return true;
}
}
}
else
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex == INDEX_NONE)
{
//Not a NewVariable is the VarName from a Timeline?
const int32 TimelineIndex = FBlueprintEditorUtils::FindTimelineIndex(Blueprint,VarName);
if (TimelineIndex == INDEX_NONE)
{
//Not a Timeline is this a SCS Node?
if (USCS_Node* Desc = FBlueprintEditorUtils::FindSCS_Node(Blueprint,VarName))
{
const int32 EntryIndex = Desc->FindMetaDataEntryIndexForKey(MetaDataKey);
if (EntryIndex != INDEX_NONE)
{
OutMetaDataValue = Desc->GetMetaData(MetaDataKey);
return true;
}
}
}
else
{
UTimelineTemplate& Desc = *Blueprint->Timelines[TimelineIndex];
int32 EntryIndex = Desc.FindMetaDataEntryIndexForKey(MetaDataKey);
if (EntryIndex != INDEX_NONE)
{
OutMetaDataValue = Desc.GetMetaData(MetaDataKey);
return true;
}
}
}
else
{
const FBPVariableDescription& Desc = Blueprint->NewVariables[VarIndex];
int32 EntryIndex = Desc.FindMetaDataEntryIndexForKey(MetaDataKey);
if (EntryIndex != INDEX_NONE)
{
OutMetaDataValue = Desc.GetMetaData(MetaDataKey);
return true;
}
}
}
OutMetaDataValue.Empty();
return false;
}
void FBlueprintEditorUtils::RemoveBlueprintVariableMetaData(UBlueprint* Blueprint, const FName& VarName, const UStruct* InLocalVarScope, const FName& MetaDataKey)
{
// If there is a local var scope, we know we are looking at a local variable
if(InLocalVarScope)
{
if(FBPVariableDescription* LocalVariable = FindLocalVariable(Blueprint, InLocalVarScope, VarName))
{
LocalVariable->RemoveMetaData(MetaDataKey);
}
}
else
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex == INDEX_NONE)
{
//Not a NewVariable is the VarName from a Timeline?
const int32 TimelineIndex = FBlueprintEditorUtils::FindTimelineIndex(Blueprint,VarName);
if (TimelineIndex == INDEX_NONE)
{
//Not a Timeline is this a SCS Node?
if (USCS_Node* SCSNode = FBlueprintEditorUtils::FindSCS_Node(Blueprint, VarName))
{
SCSNode->RemoveMetaData(MetaDataKey);
}
}
else
{
Blueprint->Timelines[TimelineIndex]->RemoveMetaData(MetaDataKey);
}
}
else
{
Blueprint->NewVariables[VarIndex].RemoveMetaData(MetaDataKey);
FProperty* Property = FindFProperty<FProperty>(Blueprint->SkeletonGeneratedClass, VarName);
if (Property)
{
Property->RemoveMetaData(MetaDataKey);
}
Property = FindFProperty<FProperty>(Blueprint->GeneratedClass, VarName);
if (Property)
{
Property->RemoveMetaData(MetaDataKey);
}
}
}
FMetaDataDependencyHelper::OnChange(Blueprint, MetaDataKey);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
void FBlueprintEditorUtils::SetBlueprintVariableCategory(UBlueprint* Blueprint, const FName& VarName, const UStruct* InLocalVarScope, const FText& NewCategory, bool bDontRecompile)
{
if (Blueprint == nullptr)
{
return;
}
// Ensure we always set a category
FText SetCategory = NewCategory;
if (SetCategory.IsEmpty())
{
SetCategory = UEdGraphSchema_K2::VR_DefaultCategory;
}
const FText OldCategory = GetBlueprintVariableCategory(Blueprint, VarName, InLocalVarScope);
if (OldCategory.EqualTo(SetCategory))
{
return;
}
const FScopedTransaction Transaction(LOCTEXT("ChangeVariableCategory", "Change Variable Category"));
Blueprint->Modify();
UClass* SkeletonGeneratedClass = Blueprint->SkeletonGeneratedClass;
if (FProperty* TargetProperty = FindFProperty<FProperty>(SkeletonGeneratedClass, VarName))
{
UClass* OuterClass = TargetProperty->GetOwnerChecked<UClass>();
const bool bIsNativeVar = (OuterClass->ClassGeneratedBy == nullptr);
if (!bIsNativeVar)
{
if (UTimelineTemplate* Timeline = Blueprint->FindTimelineTemplateByVariableName(TargetProperty->GetFName()))
{
Timeline->SetMetaData(TEXT("Category"), SetCategory.ToString());
}
else if (UBaseWidgetBlueprint* WidgetBP = Cast<UBaseWidgetBlueprint>(Blueprint))
{
WidgetBP->ForEachSourceWidget([&](UWidget* InWidget) {
if (InWidget->GetFName() == VarName)
{
InWidget->SetCategoryName(SetCategory.ToString());
}
});
}
else
{
TargetProperty->SetMetaData(TEXT("Category"), *SetCategory.ToString());
}
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex != INDEX_NONE)
{
Blueprint->NewVariables[VarIndex].Category = MoveTemp(SetCategory);
}
else
{
if (USCS_Node* SCSNode = FBlueprintEditorUtils::FindSCS_Node(Blueprint, VarName))
{
SCSNode->Modify();
SCSNode->CategoryName = MoveTemp(SetCategory);
}
}
}
}
else if (InLocalVarScope)
{
UK2Node_FunctionEntry* OutFunctionEntryNode;
if (FBPVariableDescription* LocalVariable = FindLocalVariable(Blueprint, InLocalVarScope, VarName, &OutFunctionEntryNode))
{
OutFunctionEntryNode->Modify();
LocalVariable->SetMetaData(TEXT("Category"), *SetCategory.ToString());
LocalVariable->Category = MoveTemp(SetCategory);
}
}
if (bDontRecompile == false)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
void FBlueprintEditorUtils::SetBlueprintFunctionOrMacroCategory(UEdGraph* Graph, const FText& InCategoryName, bool bDontRecompile)
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(Graph);
if (FKismetUserDeclaredFunctionMetadata* MetaData = FBlueprintEditorUtils::GetGraphFunctionMetaData(Graph))
{
const FText& NewCategory = InCategoryName.IsEmpty() ? UEdGraphSchema_K2::VR_DefaultCategory : InCategoryName;
if (!MetaData->Category.EqualTo(NewCategory))
{
FScopedTransaction Transaction(LOCTEXT("SetBlueprintFunctionOrMacroCategory", "Set Category"));
FBlueprintEditorUtils::ModifyFunctionMetaData(Graph);
UFunction* Function = nullptr;
for (TFieldIterator<UFunction> FunctionIt(Blueprint->SkeletonGeneratedClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
if (FunctionIt->GetName() == Graph->GetName())
{
Function = *FunctionIt;
break;
}
}
MetaData->Category = NewCategory;
if (Function)
{
check(!Function->IsNative()); // Should never get here with a native function, as we wouldn't have been able to find metadata for it
Function->Modify();
Function->SetMetaData(FBlueprintMetadata::MD_FunctionCategory, *NewCategory.ToString());
}
if (!bDontRecompile)
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
FBlueprintActionDatabase::Get().RefreshAssetActions(Blueprint);
}
}
}
UAnimGraphNode_Root* FBlueprintEditorUtils::GetAnimGraphRoot(UEdGraph* InGraph)
{
if(InGraph->GetSchema()->IsA(UAnimationGraphSchema::StaticClass()))
{
TArray<UAnimGraphNode_Root*> Roots;
InGraph->GetNodesOfClass<UAnimGraphNode_Root>(Roots);
check(Roots.Num() == 1);
return Roots[0];
}
return nullptr;
}
void FBlueprintEditorUtils::SetAnimationGraphLayerGroup(UEdGraph* InGraph, const FText& InGroupName)
{
if(InGraph->GetSchema()->IsA(UAnimationGraphSchema::StaticClass()))
{
const FName NewGroup = InGroupName.IsEmpty() ? NAME_None : FName(*InGroupName.ToString());
UAnimGraphNode_Root* Root = GetAnimGraphRoot(InGraph);
if(NewGroup != Root->Node.GetGroup())
{
FScopedTransaction Transaction(LOCTEXT("SetAnimationGraphLayerGroup", "Set Group"));
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(InGraph);
Root->Modify();
UFunction* Function = nullptr;
for (TFieldIterator<UFunction> FunctionIt(Blueprint->SkeletonGeneratedClass, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt)
{
if (FunctionIt->GetName() == InGraph->GetName())
{
Function = *FunctionIt;
break;
}
}
Root->Node.SetGroup(NewGroup);
if (Function)
{
Function->Modify();
Function->SetMetaData(FBlueprintMetadata::MD_FunctionCategory, NewGroup == NAME_None ? TEXT("") : *NewGroup.ToString());
}
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
int32 FBlueprintEditorUtils::FindIndexOfGraphInParent(UEdGraph* Graph)
{
int32 Result = INDEX_NONE;
if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph))
{
Result = Blueprint->FunctionGraphs.IndexOfByKey(Graph);
if (Result == INDEX_NONE)
{
Result = Blueprint->MacroGraphs.IndexOfByKey(Graph);
}
}
return Result;
}
bool FBlueprintEditorUtils::MoveGraphBeforeOtherGraph(UEdGraph* Graph, int32 NewIndex, bool bDontRecompile)
{
if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(Graph))
{
bool bModified = false;
const int32 OldFunctionIndex = Blueprint->FunctionGraphs.IndexOfByKey(Graph);
if (OldFunctionIndex != INDEX_NONE)
{
if ((OldFunctionIndex != NewIndex) && Blueprint->FunctionGraphs.IsValidIndex(NewIndex))
{
Blueprint->Modify();
Blueprint->FunctionGraphs.Insert(Graph, NewIndex);
Blueprint->FunctionGraphs.RemoveAt((OldFunctionIndex < NewIndex) ? OldFunctionIndex : (OldFunctionIndex + 1));
bModified = true;
}
}
const int32 OldMacroIndex = Blueprint->MacroGraphs.IndexOfByKey(Graph);
if (OldMacroIndex != INDEX_NONE)
{
if ((OldMacroIndex != NewIndex) && Blueprint->MacroGraphs.IsValidIndex(NewIndex))
{
Blueprint->Modify();
Blueprint->MacroGraphs.Insert(Graph, NewIndex);
Blueprint->MacroGraphs.RemoveAt((OldMacroIndex < NewIndex) ? OldMacroIndex : (OldMacroIndex + 1));
bModified = true;
}
}
if (bModified)
{
if (!bDontRecompile)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
else
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
return bModified;
}
return false;
}
FText FBlueprintEditorUtils::GetBlueprintVariableCategory(UBlueprint* Blueprint, const FName& VarName, const UStruct* InLocalVarScope)
{
FText CategoryName;
UClass* SkeletonGeneratedClass = Blueprint->SkeletonGeneratedClass;
FProperty* TargetProperty = FindFProperty<FProperty>(SkeletonGeneratedClass, VarName);
if(TargetProperty != nullptr)
{
CategoryName = FObjectEditorUtils::GetCategoryText(TargetProperty);
}
else if(InLocalVarScope)
{
// Check to see if it is a local variable
if(FBPVariableDescription* LocalVariable = FindLocalVariable(Blueprint, InLocalVarScope, VarName))
{
CategoryName = LocalVariable->Category;
}
}
if(CategoryName.IsEmpty() && Blueprint->SimpleConstructionScript != nullptr)
{
// Look for the variable in the SCS (in case the Blueprint has not been compiled yet)
if (USCS_Node* SCSNode = FBlueprintEditorUtils::FindSCS_Node(Blueprint, VarName))
{
CategoryName = SCSNode->CategoryName;
}
}
return CategoryName;
}
uint64* FBlueprintEditorUtils::GetBlueprintVariablePropertyFlags(UBlueprint* Blueprint, const FName& VarName)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex != INDEX_NONE)
{
return &Blueprint->NewVariables[VarIndex].PropertyFlags;
}
return nullptr;
}
FBPVariableDescription* FBlueprintEditorUtils::GetVariableFromOnRepFunction(UBlueprint* Blueprint, FName FuncName)
{
const TCHAR* OnRepPrefix = TEXT("OnRep_");
FString FuncNameStr = FuncName.ToString();
if (FuncNameStr.StartsWith(OnRepPrefix))
{
FName VarName(FuncNameStr.RightChop(FCString::Strlen(OnRepPrefix)));
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex != INDEX_NONE)
{
if (Blueprint->NewVariables[VarIndex].RepNotifyFunc == FuncName)
{
return &Blueprint->NewVariables[VarIndex];
}
}
}
return nullptr;
}
FName FBlueprintEditorUtils::GetBlueprintVariableRepNotifyFunc(UBlueprint* Blueprint, const FName& VarName)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex != INDEX_NONE)
{
return Blueprint->NewVariables[VarIndex].RepNotifyFunc;
}
return NAME_None;
}
void FBlueprintEditorUtils::SetBlueprintVariableRepNotifyFunc(UBlueprint* Blueprint, const FName& VarName, const FName& RepNotifyFunc)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex != INDEX_NONE)
{
Blueprint->NewVariables[VarIndex].RepNotifyFunc = RepNotifyFunc;
}
}
void FBlueprintEditorUtils::GetFunctionNameList(const UBlueprint* Blueprint, TSet<FName>& FunctionNames)
{
if( UClass* SkeletonClass = Blueprint->SkeletonGeneratedClass )
{
for( TFieldIterator<UFunction> FuncIt(SkeletonClass, EFieldIteratorFlags::IncludeSuper); FuncIt; ++FuncIt )
{
FunctionNames.Add( (*FuncIt)->GetFName() );
}
}
}
void FBlueprintEditorUtils::GetDelegateNameList(const UBlueprint* Blueprint, TSet<FName>& FunctionNames)
{
check(Blueprint);
for (int32 It = 0; It < Blueprint->DelegateSignatureGraphs.Num(); It++)
{
if (UEdGraph* Graph = Blueprint->DelegateSignatureGraphs[It])
{
FunctionNames.Add(Graph->GetFName());
}
}
}
UEdGraph* FBlueprintEditorUtils::GetDelegateSignatureGraphByName(UBlueprint* Blueprint, FName FunctionName)
{
if ((nullptr != Blueprint) && (FunctionName != NAME_None))
{
for (int32 It = 0; It < Blueprint->DelegateSignatureGraphs.Num(); It++)
{
if (UEdGraph* Graph = Blueprint->DelegateSignatureGraphs[It])
{
if(FunctionName == Graph->GetFName())
{
return Graph;
}
}
}
}
return nullptr;
}
// Gets a list of pins that should hidden for a given function
void FBlueprintEditorUtils::GetHiddenPinsForFunction(UEdGraph const* Graph, UFunction const* Function, TSet<FName>& HiddenPins, TSet<FName>* OutInternalPins)
{
check(Function != nullptr);
TMap<FName, FString>* MetaData = FMetaData::GetMapForObject(Function);
if (MetaData != nullptr)
{
for (TMap<FName, FString>::TConstIterator It(*MetaData); It; ++It)
{
const FName& Key = It.Key();
if (Key == FBlueprintMetadata::MD_LatentInfo)
{
HiddenPins.Add(*It.Value());
}
else if (Key == FBlueprintMetadata::MD_HidePin)
{
TArray<FString> HiddenPinNames;
It.Value().ParseIntoArray(HiddenPinNames, TEXT(","));
for (FString& HiddenPinName : HiddenPinNames)
{
HiddenPinName.TrimStartAndEndInline();
HiddenPins.Add(*HiddenPinName);
}
}
else if (Key == FBlueprintMetadata::MD_ExpandEnumAsExecs ||
Key == FBlueprintMetadata::MD_ExpandBoolAsExecs)
{
TArray<FName> EnumPinNames;
UK2Node_CallFunction::GetExpandEnumPinNames(Function, EnumPinNames);
for (const FName& EnumName : EnumPinNames)
{
HiddenPins.Add(EnumName);
}
}
else if (Key == FBlueprintMetadata::MD_InternalUseParam)
{
TArray<FString> HiddenPinNames;
It.Value().ParseIntoArray(HiddenPinNames, TEXT(","));
for (FString& HiddenPinName : HiddenPinNames)
{
HiddenPinName.TrimStartAndEndInline();
FName HiddenPinFName(*HiddenPinName);
HiddenPins.Add(HiddenPinFName);
if (OutInternalPins)
{
OutInternalPins->Add(HiddenPinFName);
}
}
}
else if (Key == FBlueprintMetadata::MD_WorldContext)
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
if(!K2Schema->IsStaticFunctionGraph(Graph))
{
bool bHasIntrinsicWorldContext = false;
UBlueprint const* CallingContext = FindBlueprintForGraph(Graph);
if (CallingContext && CallingContext->ParentClass)
{
UClass* NativeOwner = CallingContext->ParentClass;
while(NativeOwner && !NativeOwner->IsNative())
{
NativeOwner = NativeOwner->GetSuperClass();
}
if(NativeOwner)
{
bHasIntrinsicWorldContext = NativeOwner->GetDefaultObject()->ImplementsGetWorld();
}
}
// if the blueprint has world context that we can lookup with "self",
// then we can hide this pin (and default it to self)
if (bHasIntrinsicWorldContext)
{
HiddenPins.Add(*It.Value());
}
}
}
}
}
}
bool FBlueprintEditorUtils::IsPinTypeValid(const FEdGraphPinType& Type)
{
if (const UUserDefinedStruct* UDStruct = Cast<const UUserDefinedStruct>(Type.PinSubCategoryObject.Get()))
{
if (EUserDefinedStructureStatus::UDSS_UpToDate != UDStruct->Status)
{
return false;
}
}
return true;
}
void FBlueprintEditorUtils::ValidatePinConnections(const UEdGraphNode* Node, FCompilerResultsLog& MessageLog)
{
if (Node)
{
const UEdGraphSchema* Schema = Node->GetSchema();
check(Schema);
// Validate that all pins with links are actually set to valid connections.
// This is necessary because the user could change the type of the pin
for (UEdGraphPin* Pin : Node->Pins)
{
if (Pin && !Pin->bOrphanedPin)
{
for (UEdGraphPin* Link : Pin->LinkedTo)
{
const FPinConnectionResponse ConnectionResponse = Schema->CanCreateConnection(Pin, Link);
if (Link && !Link->bOrphanedPin && Link != Pin && ConnectionResponse.Response == CONNECT_RESPONSE_DISALLOW)
{
const FString ErrorMessage = FText::Format(LOCTEXT("BadConnection_ErrorFmt", "Invalid pin connection from @@ and @@: {0} (You may have changed the type after the connections were made)"), ConnectionResponse.Message).ToString();
MessageLog.Error(*ErrorMessage, Pin, Link);
}
}
}
}
}
}
void FBlueprintEditorUtils::ValidateEditorOnlyNodes(const UK2Node* Node, FCompilerResultsLog& MessageLog)
{
if(!Node)
{
return;
}
const UBlueprint* BP = Node->GetBlueprint();
const UClass* NodeClass = Node->GetClass();
const UPackage* NodeCDOPackage = NodeClass->GetDefaultObject(false) ? NodeClass->GetDefaultObject(false)->GetOutermost() : nullptr;
if(NodeCDOPackage && BP)
{
const bool bIsEditorOnlyPackage = NodeCDOPackage->HasAllPackagesFlags(PKG_EditorOnly);
const bool bIsUncookedOrDev = NodeCDOPackage->HasAnyPackageFlags(PKG_UncookedOnly | PKG_Developer);
const UClass* BlueprintClass = BP ? BP->ParentClass : nullptr;
const bool bIsEditorOnlyBlueprintBaseClass = BlueprintClass ? IsEditorOnlyObject(BlueprintClass) : false;
// Check whether the blueprint itself, or its class is marked as editor-only
if (!bIsUncookedOrDev && bIsEditorOnlyPackage && !(IsEditorOnlyObject(BP) || bIsEditorOnlyBlueprintBaseClass))
{
MessageLog.Warning(*LOCTEXT("EditorOnlyConflict_ErrorFmt", "The node '@@' is from an Editor Only module, but is placed in a runtime blueprint! K2 Nodes should only be defined in a Developer or UncookedOnly module.").ToString(), Node);
}
}
}
void FBlueprintEditorUtils::GetClassVariableList(const UBlueprint* Blueprint, TSet<FName>& VisibleVariables, bool bIncludePrivateVars)
{
// Existing variables in the parent class and above, when using the compilation manager the previous SkeletonGeneratedClass will have been cleared when
// we're regenerating the SkeletonGeneratedClass. Using this function in the skeleton pass at all is highly dubious, but I am leaving it until the
// compilation manager is on full time:
if (Blueprint->SkeletonGeneratedClass != nullptr)
{
for (TFieldIterator<FProperty> PropertyIt(Blueprint->SkeletonGeneratedClass, EFieldIteratorFlags::IncludeSuper); PropertyIt; ++PropertyIt)
{
FProperty* Property = *PropertyIt;
if ((!Property->HasAnyPropertyFlags(CPF_Parm) && (bIncludePrivateVars || Property->HasAllPropertyFlags(CPF_BlueprintVisible))))
{
VisibleVariables.Add(Property->GetFName());
}
}
if (bIncludePrivateVars)
{
// Include SCS node variable names, timelines, and other member variables that may be pending compilation. Consider them to be "private" as they're not technically accessible for editing just yet.
TArray<UBlueprintGeneratedClass*> ParentBPStack;
UBlueprint::GetBlueprintHierarchyFromClass(Blueprint->SkeletonGeneratedClass, ParentBPStack);
for (int32 StackIndex = ParentBPStack.Num() - 1; StackIndex >= 0; --StackIndex)
{
UBlueprintGeneratedClass* ParentPBGC = ParentBPStack[StackIndex];
check(ParentPBGC);
UBlueprint* ParentBP = Cast<UBlueprint>(ParentPBGC->ClassGeneratedBy);
GetSCSVariableNameList(ParentPBGC, VisibleVariables);
if (ParentBP)
{
for (const FBPVariableDescription& Variable : ParentBP->NewVariables)
{
VisibleVariables.Add(Variable.VarName);
}
}
// Since we defer copying the timeline templates to the BPGC until compile time,
// we consider the BP (when present) to be authoritative.
const TArray<TObjectPtr<UTimelineTemplate>>& Timelines = ParentBP ? ParentBP->Timelines : ParentPBGC->Timelines;
for (UTimelineTemplate* Timeline : Timelines)
{
if (Timeline)
{
VisibleVariables.Add(Timeline->GetFName());
}
}
}
}
}
// "self" is reserved for all classes
VisibleVariables.Add(NAME_Self);
}
void FBlueprintEditorUtils::GetNewVariablesOfType( const UBlueprint* Blueprint, const FEdGraphPinType& Type, TArray<FName>& OutVars )
{
for(int32 i=0; i<Blueprint->NewVariables.Num(); i++)
{
const FBPVariableDescription& Var = Blueprint->NewVariables[i];
if(Type == Var.VarType)
{
OutVars.Add(Var.VarName);
}
}
}
void FBlueprintEditorUtils::GetLocalVariablesOfType( const UEdGraph* Graph, const FEdGraphPinType& Type, TArray<FName>& OutVars)
{
if (DoesSupportLocalVariables(Graph))
{
// Grab the function graph, so we can find the function entry node for local variables
UEdGraph* FunctionGraph = FBlueprintEditorUtils::GetTopLevelGraph(Graph);
TArray<UK2Node_FunctionEntry*> GraphNodes;
FunctionGraph->GetNodesOfClass<UK2Node_FunctionEntry>(GraphNodes);
// There should only be one entry node
check(GraphNodes.Num() == 1);
for (const FBPVariableDescription& LocalVar : GraphNodes[0]->LocalVariables)
{
if (LocalVar.VarType == Type)
{
OutVars.Add(LocalVar.VarName);
}
}
}
}
// Adds a member variable to the blueprint. It cannot mask a variable in any superclass.
bool FBlueprintEditorUtils::AddMemberVariable(UBlueprint* Blueprint, const FName& NewVarName, const FEdGraphPinType& NewVarType, const FString& DefaultValue/* = FString()*/)
{
// Don't allow vars with empty names
if(NewVarName == NAME_None)
{
return false;
}
// First we need to see if there is already a variable with that name, in this blueprint or parent class
TSet<FName> CurrentVars;
FBlueprintEditorUtils::GetClassVariableList(Blueprint, CurrentVars);
if(CurrentVars.Contains(NewVarName))
{
return false; // fail
}
Blueprint->Modify();
// Now create new variable
FBPVariableDescription NewVar;
NewVar.VarName = NewVarName;
NewVar.VarGuid = FGuid::NewGuid();
NewVar.FriendlyName = FName::NameToDisplayString( NewVarName.ToString(), (NewVarType.PinCategory == UEdGraphSchema_K2::PC_Boolean) ? true : false );
NewVar.VarType = NewVarType;
// default new vars to 'kismet read/write' and 'only editable on owning CDO'
NewVar.PropertyFlags |= (CPF_Edit | CPF_BlueprintVisible | CPF_DisableEditOnInstance);
if(NewVarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate)
{
NewVar.PropertyFlags |= CPF_BlueprintAssignable | CPF_BlueprintCallable;
}
else
{
PostSetupObjectPinType(Blueprint, NewVar);
}
NewVar.ReplicationCondition = COND_None;
NewVar.Category = UEdGraphSchema_K2::VR_DefaultCategory;
NewVar.DefaultValue = DefaultValue;
// user created variables should be none of these things
NewVar.VarType.bIsConst = false;
NewVar.VarType.bIsWeakPointer = false;
NewVar.VarType.bIsReference = false;
// Text variables, etc. should default to multiline
if (NewVarType.PinCategory == UEdGraphSchema_K2::PC_String || NewVarType.PinCategory == UEdGraphSchema_K2::PC_Text)
{
NewVar.SetMetaData(TEXT("MultiLine"), TEXT("true"));
}
Blueprint->NewVariables.Add(NewVar);
// Potentially adjust variable names for any child blueprints
FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewVarName);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return true;
}
// Removes a member variable if it was declared in this blueprint and not in a base class.
void FBlueprintEditorUtils::RemoveMemberVariable(UBlueprint* Blueprint, const FName VarName)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
if (VarIndex != INDEX_NONE)
{
if ( Blueprint->NewVariables[VarIndex].HasMetaData(FBlueprintMetadata::MD_FieldNotify) && FBlueprintEditorUtils::ImplementsInterface(Blueprint, true, UNotifyFieldValueChanged::StaticClass()) )
{
RemoveFieldNotifyFromAllMetadata(Blueprint, VarName);
}
Blueprint->NewVariables.RemoveAt(VarIndex);
FBlueprintEditorUtils::RemoveVariableNodes(Blueprint, VarName);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
// Removes the field name from the field notify metadata of all functions and variables
void FBlueprintEditorUtils::RemoveFieldNotifyFromAllMetadata(UBlueprint* Blueprint, const FName FieldName)
{
// Go through all field notify variables and remove FieldName from their metadata.
for (int32 i = 0; i < Blueprint->NewVariables.Num(); ++i)
{
FBPVariableDescription& Variable = Blueprint->NewVariables[i];
if (Variable.HasMetaData(FBlueprintMetadata::MD_FieldNotify))
{
FString FieldNotifyValues = Variable.GetMetaData(FBlueprintMetadata::MD_FieldNotify);
TArray<FString> ListOfFieldNotifies;
const TCHAR* Delimiter = TEXT("|");
FieldNotifyValues.ParseIntoArray(ListOfFieldNotifies, Delimiter);
if (ListOfFieldNotifies.Contains(FieldName.ToString()))
{
ListOfFieldNotifies.Remove(FieldName.ToString());
if (ListOfFieldNotifies.Num() > 0)
{
FBlueprintEditorUtils::SetBlueprintVariableMetaData(Blueprint, Variable.VarName, NULL, FBlueprintMetadata::MD_FieldNotify, FString::Join(ListOfFieldNotifies, Delimiter));
}
else
{
FBlueprintEditorUtils::SetBlueprintVariableMetaData(Blueprint, Variable.VarName, NULL, FBlueprintMetadata::MD_FieldNotify, TEXT(""));
}
}
}
}
}
void FBlueprintEditorUtils::BulkRemoveMemberVariables(UBlueprint* Blueprint, const TArray<FName>& VarNames)
{
const FScopedTransaction Transaction( LOCTEXT("BulkRemoveMemberVariables", "Bulk Remove Member Variables") );
Blueprint->Modify();
bool bModified = false;
for (int32 i = 0; i < VarNames.Num(); ++i)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarNames[i]);
if (VarIndex != INDEX_NONE)
{
Blueprint->NewVariables.RemoveAt(VarIndex);
FBlueprintEditorUtils::RemoveVariableNodes(Blueprint, VarNames[i]);
bModified = true;
}
}
if (bModified)
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
void FBlueprintEditorUtils::GetUsedAndUnusedVariables(UBlueprint* Blueprint, TArray<FProperty*>& OutUsedVariables, TArray<FProperty*>& OutUnusedVariables)
{
TArray<FName> VariableNames;
for (TFieldIterator<FProperty> PropertyIt(Blueprint->SkeletonGeneratedClass, EFieldIteratorFlags::ExcludeSuper); PropertyIt; ++PropertyIt)
{
FProperty* Property = *PropertyIt;
// Don't show delegate properties, there is special handling for these
const bool bDelegateProp = Property->IsA(FDelegateProperty::StaticClass()) || Property->IsA(FMulticastDelegateProperty::StaticClass());
const bool bShouldShowProp = (!Property->HasAnyPropertyFlags(CPF_Parm) && Property->HasAllPropertyFlags(CPF_BlueprintVisible) && !bDelegateProp);
if (bShouldShowProp)
{
FName VarName = Property->GetFName();
const int32 VarInfoIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VarName);
const bool bHasVarInfo = (VarInfoIndex != INDEX_NONE);
const FObjectPropertyBase* ObjectProperty = CastField<const FObjectPropertyBase>(Property);
bool bIsTimeline = ObjectProperty &&
ObjectProperty->PropertyClass &&
ObjectProperty->PropertyClass->IsChildOf(UTimelineComponent::StaticClass());
if (!bIsTimeline && bHasVarInfo && !FBlueprintEditorUtils::IsVariableUsed(Blueprint, VarName))
{
OutUnusedVariables.Add(Property);
}
else
{
OutUsedVariables.Add(Property);
}
}
}
}
FGuid FBlueprintEditorUtils::FindMemberVariableGuidByName(UBlueprint* InBlueprint, const FName InVariableName)
{
while(InBlueprint)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(InBlueprint, InVariableName);
if (VarIndex != INDEX_NONE)
{
return InBlueprint->NewVariables[VarIndex].VarGuid;
}
InBlueprint = Cast<UBlueprint>(InBlueprint->ParentClass->ClassGeneratedBy);
}
return FGuid();
}
FName FBlueprintEditorUtils::FindMemberVariableNameByGuid(UBlueprint* InBlueprint, const FGuid& InVariableGuid)
{
while(InBlueprint)
{
for(FBPVariableDescription& Variable : InBlueprint->NewVariables)
{
if(Variable.VarGuid == InVariableGuid)
{
return Variable.VarName;
}
}
InBlueprint = Cast<UBlueprint>(InBlueprint->ParentClass->ClassGeneratedBy);
}
return NAME_None;
}
void FBlueprintEditorUtils::RemoveVariableNodes(UBlueprint* Blueprint, const FName VarName, bool const bForSelfOnly, UEdGraph* LocalGraphScope)
{
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
for(TArray<UEdGraph*>::TConstIterator it(AllGraphs); it; ++it)
{
const UEdGraph* CurrentGraph = *it;
TArray<UK2Node_Variable*> GraphNodes;
CurrentGraph->GetNodesOfClass(GraphNodes);
for( TArray<UK2Node_Variable*>::TConstIterator NodeIt(GraphNodes); NodeIt; ++NodeIt )
{
UK2Node_Variable* CurrentNode = *NodeIt;
UClass* SelfClass = Blueprint->GeneratedClass;
UClass* VariableParent = CurrentNode->VariableReference.GetMemberParentClass(SelfClass);
if ((SelfClass == VariableParent) || !bForSelfOnly)
{
if(LocalGraphScope == CurrentNode->GetGraph() || LocalGraphScope == nullptr)
{
if(VarName == CurrentNode->GetVarName())
{
CurrentNode->DestroyNode();
}
}
}
}
}
}
void FBlueprintEditorUtils::RenameComponentMemberVariable(UBlueprint* Blueprint, USCS_Node* Node, const FName NewName)
{
// Should not allow renaming to "none" (UI should prevent this)
check(!NewName.IsNone());
if (!NewName.IsEqual(Node->GetVariableName(), ENameCase::CaseSensitive))
{
Blueprint->Modify();
// Validate child blueprints and adjust variable names to avoid a potential name collision
FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewName);
// Update the name
const FName OldName = Node->GetVariableName();
Node->Modify();
Node->SetVariableName(NewName);
// Rename Inheritable Component Templates
{
const FComponentKey Key(Node);
TArray<UBlueprint*> Dependents;
FindDependentBlueprints(Blueprint, Dependents);
for (UBlueprint* DepBP : Dependents)
{
UInheritableComponentHandler* InheritableComponentHandler = DepBP ? DepBP->GetInheritableComponentHandler(false) : nullptr;
if (InheritableComponentHandler && InheritableComponentHandler->GetOverridenComponentTemplate(Key))
{
InheritableComponentHandler->Modify();
InheritableComponentHandler->RefreshTemplateName(Key);
InheritableComponentHandler->MarkPackageDirty();
}
}
}
Node->NameWasModified();
// Update any existing references to the old name
if (OldName != NAME_None)
{
FBlueprintEditorUtils::ReplaceVariableReferences(Blueprint, OldName, NewName);
}
// And recompile
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
void FBlueprintEditorUtils::RenameMemberVariable(UBlueprint* Blueprint, const FName OldName, const FName NewName)
{
if (!NewName.IsNone() && !NewName.IsEqual(OldName, ENameCase::CaseSensitive))
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, OldName);
if (VarIndex != INDEX_NONE)
{
const FScopedTransaction Transaction( LOCTEXT("RenameVariable", "Rename Variable") );
Blueprint->Modify();
// Update the name
FBPVariableDescription& Variable = Blueprint->NewVariables[VarIndex];
Variable.VarName = NewName;
// If the variable has an associated OnRep function, warn the user and break the association if the name is changed
FName OnRepFuncName = Blueprint->NewVariables[VarIndex].RepNotifyFunc;
if (OnRepFuncName != NAME_None)
{
if (!VerifyUserWantsRepNotifyVariableNameChanged(OldName, OnRepFuncName))
{
// Showing the warning dialog causes the variable name text box to lose focus, which can result in this function being called again.
// The VarName is set before verifying to skip over the second call, preventing the dialog from appearing twice.
Variable.VarName = OldName;
return;
}
Blueprint->NewVariables[VarIndex].RepNotifyFunc = NAME_None;
}
Variable.FriendlyName = FName::NameToDisplayString( NewName.ToString(), (Variable.VarType.PinCategory == UEdGraphSchema_K2::PC_Boolean) ? true : false );
// Update any existing references to the old name
FBlueprintEditorUtils::ReplaceVariableReferences(Blueprint, OldName, NewName);
{
//Grab property of blueprint's current CDO
UClass* GeneratedClass = Blueprint->GeneratedClass;
UObject* GeneratedCDO = GeneratedClass ? GeneratedClass->GetDefaultObject(false) : nullptr;
if (GeneratedCDO)
{
FProperty* TargetProperty = FindFProperty<FProperty>(GeneratedCDO->GetClass(), OldName); // GeneratedCDO->GetClass() is used instead of GeneratedClass, because CDO could use REINST class.
// Grab the address of where the property is actually stored (UObject* base, plus the offset defined in the property)
void* OldPropertyAddr = TargetProperty ? TargetProperty->ContainerPtrToValuePtr<void>(GeneratedCDO) : nullptr;
if (OldPropertyAddr)
{
// if there is a property for variable, it means the original default value was already copied, so it can be safely overridden
Variable.DefaultValue.Empty();
PropertyValueToString(TargetProperty, reinterpret_cast<const uint8*>(GeneratedCDO), Variable.DefaultValue);
}
}
else
{
UE_LOG(LogBlueprint, Warning, TEXT("Could not find default value of renamed variable '%s' (previously '%s') in %s"), *NewName.ToString(), *OldName.ToString(), *GetPathNameSafe(Blueprint));
}
// Validate child blueprints and adjust variable names to avoid a potential name collision
FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewName);
// And recompile
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
{
const bool bIsDelegateVar = (Variable.VarType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate);
if (UEdGraph* DelegateSignatureGraph = bIsDelegateVar ? GetDelegateSignatureGraphByName(Blueprint, OldName) : nullptr)
{
FBlueprintEditorUtils::RenameGraph(DelegateSignatureGraph, NewName.ToString());
// this code should not be necessary, because the GUID remains valid, but let it be for backward compatibility.
TArray<UK2Node_BaseMCDelegate*> NodeUsingDelegate;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_BaseMCDelegate>(Blueprint, NodeUsingDelegate);
for (UK2Node_BaseMCDelegate* FunctionNode : NodeUsingDelegate)
{
if (FunctionNode->DelegateReference.IsSelfContext() && (FunctionNode->DelegateReference.GetMemberName() == OldName))
{
FunctionNode->Modify();
FunctionNode->DelegateReference.SetSelfMember(NewName);
}
}
}
}
}
else if (Blueprint && Blueprint->SimpleConstructionScript)
{
// Wasn't in the introduced variable list; try to find the associated SCS node
//@TODO: The SCS-generated variables should be in the variable list and have a link back;
// As it stands, you cannot do any metadata operations on a SCS variable, and you have to do icky code like the following
TArray<USCS_Node*> Nodes = Blueprint->SimpleConstructionScript->GetAllNodes();
for (TArray<USCS_Node*>::TConstIterator NodeIt(Nodes); NodeIt; ++NodeIt)
{
USCS_Node* CurrentNode = *NodeIt;
if (CurrentNode->GetVariableName() == OldName)
{
RenameComponentMemberVariable(Blueprint, CurrentNode, NewName);
break;
}
}
}
}
}
TArray<UK2Node*> FBlueprintEditorUtils::GetNodesForVariable(const FName& InVarName, const UBlueprint* InBlueprint, const UStruct* InScope/* = nullptr*/)
{
TArray<UK2Node*> ReturnNodes;
TArray<UK2Node*> Nodes;
GetAllNodesOfClass<UK2Node>(InBlueprint, Nodes);
bool bNodesPendingDeletion = false;
for( TArray<UK2Node*>::TConstIterator NodeIt(Nodes); NodeIt; ++NodeIt )
{
UK2Node* CurrentNode = *NodeIt;
if (CurrentNode->ReferencesVariable(InVarName, InScope))
{
ReturnNodes.Add(CurrentNode);
}
}
return ReturnNodes;
}
bool FBlueprintEditorUtils::VerifyUserWantsVariableTypeChanged(const FName& InVarName)
{
FFormatNamedArguments Args;
Args.Add(TEXT("VariableName"), FText::FromName(InVarName));
FText ConfirmDelete = FText::Format(LOCTEXT( "ConfirmChangeVarType", "This could break connections, do you want to search all Variable '{VariableName}' instances, change its type, and recompile?"), Args );
// Warn the user that this may result in data loss
FSuppressableWarningDialog::FSetupInfo Info( ConfirmDelete, LOCTEXT("ChangeVariableType", "Change Variable Type"), "ChangeVariableType_Warning" );
Info.ConfirmText = LOCTEXT( "ChangeVariableType_Yes", "Change Variable Type");
Info.CancelText = LOCTEXT( "ChangeVariableType_No", "Do Nothing");
FSuppressableWarningDialog ChangeVariableType( Info );
FSuppressableWarningDialog::EResult RetCode = ChangeVariableType.ShowModal();
return RetCode == FSuppressableWarningDialog::Confirm || RetCode == FSuppressableWarningDialog::Suppressed;
}
bool FBlueprintEditorUtils::VerifyUserWantsRepNotifyVariableNameChanged(const FName& InVarName, const FName& InFuncName)
{
FFormatNamedArguments Args;
Args.Add(TEXT("VariableName"), FText::FromName(InVarName));
Args.Add(TEXT("FuncName"), FText::FromName(InFuncName));
FText ConfirmRename = FText::Format(LOCTEXT("ConfirmChangeRepNotifyVarName",
"Variable '{VariableName}' is linked to the OnRep function '{FuncName}'. Renaming it will still allow this variable to be replicated, but the function will not be called. Do you wish to proceed?"), Args);
FSuppressableWarningDialog::FSetupInfo Info(ConfirmRename, LOCTEXT("ChangeRepNotifyVariableName", "Change RepNotify Variable Name"), "ChangeRepNotifyVariableName_Warning");
Info.ConfirmText = LOCTEXT("ChangeRepNotifyVariableName_Yes", "Yes");
Info.CancelText = LOCTEXT("ChangeRepNotifyVariableName_No", "No");
FSuppressableWarningDialog ChangeRepNotifyVariableName(Info);
FSuppressableWarningDialog::EResult RetCode = ChangeRepNotifyVariableName.ShowModal();
return RetCode == FSuppressableWarningDialog::Confirm || RetCode == FSuppressableWarningDialog::Suppressed;
}
void FBlueprintEditorUtils::GetLoadedChildBlueprints(UBlueprint* InBlueprint, TArray<UBlueprint*>& OutBlueprints)
{
// Iterate over currently-loaded Blueprints and potentially adjust their variable names if they conflict with the parent
for(TObjectIterator<UBlueprint> BlueprintIt; BlueprintIt; ++BlueprintIt)
{
UBlueprint* ChildBP = *BlueprintIt;
if(ChildBP != nullptr && ChildBP->ParentClass != nullptr)
{
TArray<UBlueprint*> ParentBPArray;
// Get the parent hierarchy
UBlueprint::GetBlueprintHierarchyFromClass(ChildBP->ParentClass, ParentBPArray);
// Also get any BP interfaces we use
TArray<UClass*> ImplementedInterfaces;
FindImplementedInterfaces(ChildBP, true, ImplementedInterfaces);
for (UClass* ImplementedInterface : ImplementedInterfaces)
{
UBlueprint* BlueprintInterfaceClass = UBlueprint::GetBlueprintFromClass(ImplementedInterface);
if(BlueprintInterfaceClass != nullptr)
{
ParentBPArray.Add(BlueprintInterfaceClass);
}
}
if(ParentBPArray.Contains(InBlueprint))
{
OutBlueprints.Add(ChildBP);
}
}
}
}
void FBlueprintEditorUtils::ChangeMemberVariableType(UBlueprint* Blueprint, const FName VariableName, const FEdGraphPinType& NewPinType)
{
if (VariableName != NAME_None)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, VariableName);
if (VarIndex != INDEX_NONE)
{
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
FBPVariableDescription& Variable = Blueprint->NewVariables[VarIndex];
// Update the variable type only if it is different
if (Variable.VarType != NewPinType)
{
TArray<UBlueprint*> ChildBPs;
GetLoadedChildBlueprints(Blueprint, ChildBPs);
TArray<UK2Node*> AllVariableNodes = GetNodesForVariable(VariableName, Blueprint);
for(UBlueprint* ChildBP : ChildBPs)
{
TArray<UK2Node*> VariableNodes = GetNodesForVariable(VariableName, ChildBP);
AllVariableNodes.Append(VariableNodes);
}
// TRUE if the user might be breaking variable connections
bool bBreakingVariableConnections = false;
// If there are variable nodes in place, warn the user of the consequences using a suppressible dialog
if(AllVariableNodes.Num())
{
if(!VerifyUserWantsVariableTypeChanged(VariableName))
{
// User has decided to cancel changing the variable member type
return;
}
bBreakingVariableConnections = true;
}
const FScopedTransaction Transaction( LOCTEXT("ChangeVariableType", "Change Variable Type") );
Blueprint->Modify();
/** Only change the variable type if type selection is valid, some unloaded Blueprints will turn out to be bad */
bool bChangeVariableType = true;
if ((NewPinType.PinCategory == UEdGraphSchema_K2::PC_Object) || (NewPinType.PinCategory == UEdGraphSchema_K2::PC_Interface))
{
// if it's a PC_Object, then it should have an associated UClass object
if(NewPinType.PinSubCategoryObject.IsValid())
{
const UClass* ClassObject = Cast<UClass>(NewPinType.PinSubCategoryObject.Get());
check(ClassObject != nullptr);
if (ClassObject->IsChildOf(AActor::StaticClass()))
{
// NOTE: Right now the code that stops hard AActor references from being set in unsafe places is tied to this flag
Variable.PropertyFlags |= CPF_DisableEditOnTemplate;
}
else
{
// clear the disable-default-value flag that might have been present (if this was an AActor variable before)
Variable.PropertyFlags &= ~(CPF_DisableEditOnTemplate);
}
}
else
{
bChangeVariableType = false;
// Display a notification to inform the user that the variable type was invalid (likely due to corruption), it should no longer appear in the list.
FNotificationInfo Info( LOCTEXT("InvalidUnloadedBP", "The selected type was invalid once loaded, it has been removed from the list!") );
Info.ExpireDuration = 3.0f;
Info.bUseLargeFont = false;
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
if ( Notification.IsValid() )
{
Notification->SetCompletionState( SNotificationItem::CS_Fail );
}
}
}
else
{
// clear the disable-default-value flag that might have been present (if this was an AActor variable before)
Variable.PropertyFlags &= ~(CPF_DisableEditOnTemplate);
}
if(bChangeVariableType)
{
const bool bBecameBoolean = Variable.VarType.PinCategory != UEdGraphSchema_K2::PC_Boolean && NewPinType.PinCategory == UEdGraphSchema_K2::PC_Boolean;
const bool bBecameNotBoolean = Variable.VarType.PinCategory == UEdGraphSchema_K2::PC_Boolean && NewPinType.PinCategory != UEdGraphSchema_K2::PC_Boolean;
if (bBecameBoolean || bBecameNotBoolean)
{
Variable.FriendlyName = FName::NameToDisplayString(Variable.VarName.ToString(), bBecameBoolean);
}
Variable.VarType = NewPinType;
if(Variable.VarType.IsSet() || Variable.VarType.IsMap())
{
// Make sure that the variable is no longer tagged for replication, and warn the user if the variable is no
// longer going to be replicated:
if(Variable.RepNotifyFunc != NAME_None || Variable.PropertyFlags & CPF_Net || Variable.PropertyFlags & CPF_RepNotify)
{
FNotificationInfo Warning(
FText::Format(
LOCTEXT("InvalidReplicationSettings", "Maps and sets cannot be replicated - {0} has had its replication settings cleared"),
FText::FromName(Variable.VarName)
)
);
Warning.ExpireDuration = 5.0f;
Warning.bFireAndForget = true;
Warning.Image = FCoreStyle::Get().GetBrush(TEXT("MessageLog.Warning"));
FSlateNotificationManager::Get().AddNotification(Warning);
Variable.PropertyFlags &= ~CPF_Net;
Variable.PropertyFlags &= ~CPF_RepNotify;
Variable.RepNotifyFunc = NAME_None;
Variable.ReplicationCondition = COND_None;
}
}
UClass* ParentClass = nullptr;
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
if(bBreakingVariableConnections)
{
for(UBlueprint* ChildBP : ChildBPs)
{
// Mark the Blueprint as structurally modified so we can reconstruct the node successfully
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(ChildBP);
}
// Reconstruct all variable nodes that reference the changing variable
for(UK2Node* VariableNode : AllVariableNodes)
{
K2Schema->ReconstructNode(*VariableNode, true);
}
TSharedPtr<IToolkit> FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(Blueprint);
if (FoundAssetEditor.IsValid())
{
TSharedRef<IBlueprintEditor> BlueprintEditor = StaticCastSharedRef<IBlueprintEditor>(FoundAssetEditor.ToSharedRef());
UK2Node* FirstVariableNode = nullptr;
for (UK2Node* VariableNode : AllVariableNodes)
{
if (VariableNode->IsA<UK2Node_Variable>())
{
FirstVariableNode = VariableNode;
break;
}
}
if (FirstVariableNode)
{
constexpr bool bSetFindWithinBlueprint = false;
constexpr bool bSelectFirstResult = false;
constexpr EGetFindReferenceSearchStringFlags Flags = EGetFindReferenceSearchStringFlags::UseSearchSyntax;
BlueprintEditor->SummonSearchUI(bSetFindWithinBlueprint, FirstVariableNode->GetFindReferenceSearchString(Flags), bSelectFirstResult);
}
}
}
}
}
}
}
}
FName FBlueprintEditorUtils::DuplicateMemberVariable(UBlueprint* InFromBlueprint, UBlueprint* InToBlueprint, FName InVariableToDuplicate)
{
FName DuplicatedVariableName;
if (InVariableToDuplicate != NAME_None)
{
const FScopedTransaction Transaction(LOCTEXT("DuplicateVariable", "Duplicate Variable"));
InToBlueprint->Modify();
FBPVariableDescription NewVar;
UBlueprint* SourceBlueprint;
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndexAndBlueprint(InFromBlueprint, InVariableToDuplicate, SourceBlueprint);
if (VarIndex != INDEX_NONE)
{
FBPVariableDescription& Variable = SourceBlueprint->NewVariables[VarIndex];
NewVar = DuplicateVariableDescription(SourceBlueprint, Variable);
// We need to manually pull the DefaultValue from the FProperty to set it
void* OldPropertyAddr = nullptr;
//Grab property of blueprint's current CDO
UClass* GeneratedClass = SourceBlueprint->GeneratedClass;
UObject* GeneratedCDO = GeneratedClass->GetDefaultObject();
FProperty* TargetProperty = FindFProperty<FProperty>(GeneratedClass, Variable.VarName);
if (TargetProperty)
{
// Grab the address of where the property is actually stored (UObject* base, plus the offset defined in the property)
OldPropertyAddr = TargetProperty->ContainerPtrToValuePtr<void>(GeneratedCDO);
if (OldPropertyAddr)
{
// if there is a property for variable, it means the original default value was already copied, so it can be safely overridden
NewVar.DefaultValue.Empty();
TargetProperty->ExportTextItem_Direct(NewVar.DefaultValue, OldPropertyAddr, OldPropertyAddr, nullptr, PPF_SerializedAsImportText);
}
}
// Add the new variable
InToBlueprint->NewVariables.Add(NewVar);
}
if (NewVar.VarGuid.IsValid())
{
DuplicatedVariableName = NewVar.VarName;
// Potentially adjust variable names for any child blueprints
FBlueprintEditorUtils::ValidateBlueprintChildVariables(InToBlueprint, NewVar.VarName);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InToBlueprint);
}
}
return DuplicatedVariableName;
}
FName FBlueprintEditorUtils::DuplicateVariable(UBlueprint* InBlueprint, const UStruct* InScope, FName InVariableToDuplicate)
{
FName DuplicatedVariableName;
if (InVariableToDuplicate != NAME_None)
{
const FScopedTransaction Transaction(LOCTEXT("DuplicateVariable", "Duplicate Variable"));
InBlueprint->Modify();
DuplicatedVariableName = FBlueprintEditorUtils::DuplicateMemberVariable(InBlueprint, InBlueprint, InVariableToDuplicate);
if (DuplicatedVariableName == NAME_None && InScope)
{
// It's probably a local variable
UK2Node_FunctionEntry* FunctionEntry = nullptr;
FBPVariableDescription* LocalVariable = FBlueprintEditorUtils::FindLocalVariable(InBlueprint, InScope, InVariableToDuplicate, &FunctionEntry);
FBPVariableDescription NewVar;
if (LocalVariable)
{
FunctionEntry->Modify();
NewVar = DuplicateVariableDescription(InBlueprint, *LocalVariable);
// Add the new variable
FunctionEntry->LocalVariables.Add(NewVar);
}
if (NewVar.VarGuid.IsValid())
{
DuplicatedVariableName = NewVar.VarName;
// Potentially adjust variable names for any child blueprints
FBlueprintEditorUtils::ValidateBlueprintChildVariables(InBlueprint, NewVar.VarName);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint);
}
}
}
return DuplicatedVariableName;
}
FBPVariableDescription FBlueprintEditorUtils::DuplicateVariableDescription(UBlueprint* InBlueprint, FBPVariableDescription& InVariableDescription)
{
FName DuplicatedVariableName = InVariableDescription.VarName;
if (FKismetNameValidator(InBlueprint).IsValid(DuplicatedVariableName) != EValidatorResult::Ok)
{
DuplicatedVariableName = FindUniqueKismetName(InBlueprint, InVariableDescription.VarName.GetPlainNameString());
}
// Now create new variable
FBPVariableDescription NewVar = InVariableDescription;
NewVar.VarName = DuplicatedVariableName;
NewVar.FriendlyName = FName::NameToDisplayString( NewVar.VarName.ToString(), NewVar.VarType.PinCategory == UEdGraphSchema_K2::PC_Boolean);
NewVar.VarGuid = FGuid::NewGuid();
return NewVar;
}
bool FBlueprintEditorUtils::AddLocalVariable(UBlueprint* Blueprint, UEdGraph* InTargetGraph, const FName InNewVarName, const FEdGraphPinType& InNewVarType, const FString& DefaultValue/*= FString()*/)
{
if(InTargetGraph != nullptr && InTargetGraph->GetSchema()->GetGraphType(InTargetGraph) == GT_Function)
{
// Ensure we have the top level graph for the function, in-case we are in a child graph
UEdGraph* TargetGraph = FBlueprintEditorUtils::GetTopLevelGraph(InTargetGraph);
const FScopedTransaction Transaction( LOCTEXT("AddLocalVariable", "Add Local Variable") );
Blueprint->Modify();
TArray<UK2Node_FunctionEntry*> FunctionEntryNodes;
TargetGraph->GetNodesOfClass(FunctionEntryNodes);
check(FunctionEntryNodes.Num());
// Now create new variable
FBPVariableDescription NewVar;
NewVar.VarName = InNewVarName;
NewVar.VarGuid = FGuid::NewGuid();
NewVar.VarType = InNewVarType;
NewVar.PropertyFlags |= CPF_BlueprintVisible;
NewVar.FriendlyName = FName::NameToDisplayString( NewVar.VarName.ToString(), (NewVar.VarType.PinCategory == UEdGraphSchema_K2::PC_Boolean) ? true : false );
NewVar.Category = UEdGraphSchema_K2::VR_DefaultCategory;
NewVar.DefaultValue = DefaultValue;
PostSetupObjectPinType(Blueprint, NewVar);
FunctionEntryNodes[0]->Modify();
FunctionEntryNodes[0]->LocalVariables.Add(NewVar);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return true;
}
return false;
}
void FBlueprintEditorUtils::RemoveLocalVariable(UBlueprint* InBlueprint, const UStruct* InScope, const FName InVarName)
{
UEdGraph* ScopeGraph = FindScopeGraph(InBlueprint, InScope);
if(ScopeGraph)
{
TArray<UK2Node_FunctionEntry*> GraphNodes;
ScopeGraph->GetNodesOfClass<UK2Node_FunctionEntry>(GraphNodes);
bool bFoundLocalVariable = false;
// There is only ever 1 function entry
check(GraphNodes.Num() == 1)
for( int32 VarIdx = 0; VarIdx < GraphNodes[0]->LocalVariables.Num(); ++VarIdx )
{
if(GraphNodes[0]->LocalVariables[VarIdx].VarName == InVarName)
{
GraphNodes[0]->LocalVariables.RemoveAt(VarIdx);
FBlueprintEditorUtils::RemoveVariableNodes(InBlueprint, InVarName, true, ScopeGraph);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint);
bFoundLocalVariable = true;
// No other local variables will match, we are done
break;
}
}
// Check if we found the local variable, it is a problem if we do not.
if(!bFoundLocalVariable)
{
UE_LOG(LogBlueprint, Warning, TEXT("Could not find local variable '%s'!"), *InVarName.ToString());
}
}
}
FFunctionFromNodeHelper::FFunctionFromNodeHelper(const UObject* Obj) : Function(FunctionFromNode(Cast<UK2Node>(Obj))), Node(Cast<UK2Node>(Obj))
{
}
UFunction* FFunctionFromNodeHelper::FunctionFromNode(const UK2Node* Node)
{
UFunction* Function = nullptr;
UBlueprint* Blueprint = Node ? Node->GetBlueprint() : nullptr;
const UClass* SearchScope = Blueprint ? Blueprint->SkeletonGeneratedClass : nullptr;
if (SearchScope)
{
if (const UK2Node_FunctionResult* ResultNode = Cast<UK2Node_FunctionResult>(Node))
{
// Function result nodes cannot resolve the UFunction, so find the entry node and use that for finding the UFunction
TArray<UK2Node_FunctionEntry*> EntryNodes;
ResultNode->GetGraph()->GetNodesOfClass(EntryNodes);
check(EntryNodes.Num() == 1);
Node = EntryNodes[0];
}
if (const UK2Node_FunctionEntry* FunctionNode = Cast<UK2Node_FunctionEntry>(Node))
{
const FName FunctionName = (FunctionNode->CustomGeneratedFunctionName != NAME_None) ? FunctionNode->CustomGeneratedFunctionName : FunctionNode->GetGraph()->GetFName();
Function = SearchScope->FindFunctionByName(FunctionName);
}
else if (const UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node))
{
// We need to search up the class hierarchy by name or functions like CanAddParentNode will fail:
FName SearchName = EventNode->EventReference.GetMemberName();
// If the function member name is none, then try the custom function name
if (SearchName.IsNone())
{
SearchName = EventNode->CustomFunctionName;
}
Function = SearchScope->FindFunctionByName(SearchName);
}
}
return Function;
}
UEdGraph* FBlueprintEditorUtils::FindScopeGraph(const UBlueprint* InBlueprint, const UStruct* InScope)
{
UEdGraph* ScopeGraph = nullptr;
TArray<UEdGraph*> AllGraphs;
InBlueprint->GetAllGraphs(AllGraphs);
for (UEdGraph* Graph : AllGraphs)
{
check(Graph != nullptr);
if(Graph->GetFName() == InScope->GetFName())
{
// This graph should always be a function graph
check(Graph->GetSchema()->GetGraphType(Graph) == GT_Function);
ScopeGraph = Graph;
break;
}
}
if(!ScopeGraph)
{
FName UbergraphName = FBlueprintEditorUtils::GetUbergraphFunctionName(InBlueprint);
bool bIsBlueprintEvent = false;
if (const UFunction* AsFunction = Cast<UFunction>(InScope))
{
bIsBlueprintEvent = AsFunction->HasAnyFunctionFlags(FUNC_BlueprintEvent);
}
if(InScope->GetFName() == UbergraphName || bIsBlueprintEvent)
{
if(InBlueprint->UbergraphPages.Num() > 0)
{
ScopeGraph = InBlueprint->UbergraphPages[0];
}
}
}
return ScopeGraph;
}
void FBlueprintEditorUtils::RenameLocalVariable(UBlueprint* InBlueprint, const UStruct* InScope, const FName InOldName, const FName InNewName)
{
if (!InNewName.IsNone() && !InNewName.IsEqual(InOldName, ENameCase::CaseSensitive))
{
UK2Node_FunctionEntry* FunctionEntry = nullptr;
FBPVariableDescription* LocalVariable = FindLocalVariable(InBlueprint, InScope, InOldName, &FunctionEntry);
const FProperty* OldProperty = FindFProperty<const FProperty>(InScope, InOldName);
const FProperty* ExistingProperty = FindFProperty<const FProperty>(InScope, InNewName);
const bool bHasExistingProperty = ExistingProperty && ExistingProperty != OldProperty;
if (bHasExistingProperty)
{
UE_LOG(LogBlueprint, Warning, TEXT("Cannot name local variable '%s'. The name is already used."), *InNewName.ToString());
}
if (LocalVariable && !bHasExistingProperty)
{
const FScopedTransaction Transaction( LOCTEXT("RenameLocalVariable", "Rename Local Variable") );
InBlueprint->Modify();
FunctionEntry->Modify();
// Update the name
LocalVariable->VarName = InNewName;
LocalVariable->FriendlyName = FName::NameToDisplayString( InNewName.ToString(), LocalVariable->VarType.PinCategory == UEdGraphSchema_K2::PC_Boolean );
// Update any existing references to the old name
if (RenameVariableReferencesInGraph(InBlueprint, InBlueprint->GeneratedClass, FindScopeGraph(InBlueprint, InScope), InOldName, InNewName))
{
MarkBlueprintAsModified(InBlueprint);
}
// Validate child blueprints and adjust variable names to avoid a potential name collision
FBlueprintEditorUtils::ValidateBlueprintChildVariables(InBlueprint, InNewName);
// And recompile
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint);
}
}
}
FBPVariableDescription* FBlueprintEditorUtils::FindLocalVariable(UBlueprint* InBlueprint, const UStruct* InScope, const FName InVariableName)
{
UK2Node_FunctionEntry* DummyFunctionEntry = nullptr;
return FindLocalVariable(InBlueprint, InScope, InVariableName, &DummyFunctionEntry);
}
FBPVariableDescription* FBlueprintEditorUtils::FindLocalVariable(const UBlueprint* InBlueprint, const UEdGraph* InScopeGraph, const FName InVariableName, class UK2Node_FunctionEntry** OutFunctionEntry)
{
FBPVariableDescription* ReturnVariable = nullptr;
if (DoesSupportLocalVariables(InScopeGraph))
{
UEdGraph* FunctionGraph = GetTopLevelGraph(InScopeGraph);
TArray<UK2Node_FunctionEntry*> GraphNodes;
FunctionGraph->GetNodesOfClass<UK2Node_FunctionEntry>(GraphNodes);
bool bFoundLocalVariable = false;
if (GraphNodes.Num() > 0)
{
// If there is an entry node, there should only be one
check(GraphNodes.Num() == 1);
for (int32 VarIdx = 0; VarIdx < GraphNodes[0]->LocalVariables.Num(); ++VarIdx)
{
if (GraphNodes[0]->LocalVariables[VarIdx].VarName == InVariableName)
{
if (OutFunctionEntry)
{
*OutFunctionEntry = GraphNodes[0];
}
ReturnVariable = &GraphNodes[0]->LocalVariables[VarIdx];
break;
}
}
}
}
return ReturnVariable;
}
FBPVariableDescription* FBlueprintEditorUtils::FindLocalVariable(const UBlueprint* InBlueprint, const UStruct* InScope, const FName InVariableName, class UK2Node_FunctionEntry** OutFunctionEntry)
{
UEdGraph* ScopeGraph = FindScopeGraph(InBlueprint, InScope);
return FindLocalVariable(InBlueprint, ScopeGraph, InVariableName, OutFunctionEntry);
}
FName FBlueprintEditorUtils::FindLocalVariableNameByGuid(UBlueprint* InBlueprint, const FGuid& InVariableGuid)
{
// Search through all function entry nodes for a local variable with the passed Guid
TArray<UK2Node_FunctionEntry*> FunctionEntryNodes;
GetAllNodesOfClass<UK2Node_FunctionEntry>(InBlueprint, FunctionEntryNodes);
FName ReturnVariableName = NAME_None;
for (UK2Node_FunctionEntry* const FunctionEntry : FunctionEntryNodes)
{
for( FBPVariableDescription& Variable : FunctionEntry->LocalVariables )
{
if(Variable.VarGuid == InVariableGuid)
{
ReturnVariableName = Variable.VarName;
break;
}
}
if(ReturnVariableName != NAME_None)
{
break;
}
}
return ReturnVariableName;
}
FGuid FBlueprintEditorUtils::FindLocalVariableGuidByName(UBlueprint* InBlueprint, const UStruct* InScope, const FName InVariableName)
{
FGuid ReturnGuid;
if(FBPVariableDescription* LocalVariable = FindLocalVariable(InBlueprint, InScope, InVariableName))
{
ReturnGuid = LocalVariable->VarGuid;
}
return ReturnGuid;
}
FGuid FBlueprintEditorUtils::FindLocalVariableGuidByName(UBlueprint* InBlueprint, const UEdGraph* InScopeGraph, const FName InVariableName)
{
FGuid ReturnGuid;
if(FBPVariableDescription* LocalVariable = FindLocalVariable(InBlueprint, InScopeGraph, InVariableName))
{
ReturnGuid = LocalVariable->VarGuid;
}
return ReturnGuid;
}
void FBlueprintEditorUtils::ChangeLocalVariableType(UBlueprint* InBlueprint, const UStruct* InScope, const FName InVariableName, const FEdGraphPinType& NewPinType)
{
if (InVariableName != NAME_None)
{
FString ActionCategory;
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
UK2Node_FunctionEntry* FunctionEntry = nullptr;
FBPVariableDescription* VariablePtr = FindLocalVariable(InBlueprint, InScope, InVariableName, &FunctionEntry);
if(VariablePtr)
{
FBPVariableDescription& Variable = *VariablePtr;
// Update the variable type only if it is different
if (Variable.VarName == InVariableName && Variable.VarType != NewPinType)
{
TArray<UK2Node*> VariableNodes = GetNodesForVariable(InVariableName, InBlueprint, InScope);
// If there are variable nodes in place, warn the user of the consequences using a suppressible dialog
if(VariableNodes.Num())
{
if(!VerifyUserWantsVariableTypeChanged(InVariableName))
{
// User has decided to cancel changing the variable member type
return;
}
}
const FScopedTransaction Transaction( LOCTEXT("ChangeLocalVariableType", "Change Local Variable Type") );
InBlueprint->Modify();
FunctionEntry->Modify();
Variable.VarType = NewPinType;
// Reset the default value
Variable.DefaultValue.Empty();
// Mark the Blueprint as structurally modified so we can reconstruct the node successfully
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(InBlueprint);
if ((NewPinType.PinCategory == UEdGraphSchema_K2::PC_Object) || (NewPinType.PinCategory == UEdGraphSchema_K2::PC_Interface) || (NewPinType.PinCategory == UEdGraphSchema_K2::PC_SoftObject))
{
// if it's a PC_Object, then it should have an associated UClass object
if(NewPinType.PinSubCategoryObject.IsValid())
{
const UClass* ClassObject = Cast<UClass>(NewPinType.PinSubCategoryObject.Get());
check(ClassObject != nullptr);
if (ClassObject->IsChildOf(AActor::StaticClass()))
{
// prevent Actor variables from having default values (because Blueprint templates are library elements that can
// bridge multiple levels and different levels might not have the actor that the default is referencing).
Variable.PropertyFlags |= CPF_DisableEditOnTemplate;
}
else
{
// clear the disable-default-value flag that might have been present (if this was an AActor variable before)
Variable.PropertyFlags &= ~(CPF_DisableEditOnTemplate);
}
}
}
// Reconstruct all local variables referencing the modified one
for(UK2Node* VariableNode : VariableNodes)
{
K2Schema->ReconstructNode(*VariableNode, true);
}
TSharedPtr<IToolkit> FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(InBlueprint);
// No need to submit a search query if there are no nodes.
if (FoundAssetEditor.IsValid() && VariableNodes.Num())
{
TSharedRef<IBlueprintEditor> BlueprintEditor = StaticCastSharedRef<IBlueprintEditor>(FoundAssetEditor.ToSharedRef());
UK2Node* FirstVariableNode = nullptr;
for (UK2Node* VariableNode : VariableNodes)
{
if (VariableNode->IsA<UK2Node_Variable>())
{
FirstVariableNode = VariableNode;
break;
}
}
if (FirstVariableNode)
{
constexpr bool bSetFindWithinBlueprint = true;
constexpr bool bSelectFirstResult = false;
constexpr EGetFindReferenceSearchStringFlags Flags = EGetFindReferenceSearchStringFlags::UseSearchSyntax;
BlueprintEditor->SummonSearchUI(bSetFindWithinBlueprint, VariableNodes[0]->GetFindReferenceSearchString(Flags), bSelectFirstResult);
}
}
}
}
}
}
void FBlueprintEditorUtils::ReplaceVariableReferences(UBlueprint* Blueprint, const FName OldName, const FName NewName)
{
check((OldName != NAME_None) && (NewName != NAME_None));
FBlueprintEditorUtils::RenameVariableReferences(Blueprint, Blueprint->GeneratedClass, OldName, NewName);
TArray<UBlueprint*> Dependents;
FindDependentBlueprints(Blueprint, Dependents);
for (UBlueprint* DependentBp : Dependents)
{
FBlueprintEditorUtils::RenameVariableReferences(DependentBp, Blueprint->GeneratedClass, OldName, NewName);
}
}
void FBlueprintEditorUtils::ReplaceVariableReferences(UBlueprint* Blueprint, const FProperty* OldVariable, const FProperty* NewVariable)
{
check((OldVariable != nullptr) && (NewVariable != nullptr));
ReplaceVariableReferences(Blueprint, OldVariable->GetFName(), NewVariable->GetFName());
}
void FBlueprintEditorUtils::ReplaceFunctionReferences(UBlueprint* Blueprint, const FName OldName, const FName NewName)
{
check((OldName != NAME_None) && (NewName != NAME_None));
FBlueprintEditorUtils::RenameFunctionReferences(Blueprint, Blueprint->GeneratedClass, OldName, NewName);
TArray<UBlueprint*> Dependents;
FindDependentBlueprints(Blueprint, Dependents);
for (UBlueprint* DependentBp : Dependents)
{
FBlueprintEditorUtils::RenameFunctionReferences(DependentBp, Blueprint->GeneratedClass, OldName, NewName);
}
}
bool FBlueprintEditorUtils::IsVariableComponent(const FBPVariableDescription& Variable)
{
// Find the variable in the list
if( Variable.VarType.PinCategory == FName(TEXT("object")) )
{
const UClass* VarClass = Cast<const UClass>(Variable.VarType.PinSubCategoryObject.Get());
return (VarClass && VarClass->HasAnyClassFlags(CLASS_DefaultToInstanced));
}
return false;
}
namespace UE::Blueprint::Private
{
// Given a specified search criteria algorithm, walk the current blueprint with a specified scope.
// When no explicit scope is provided, the asset registry will also be walked with the algorithm on additional blueprints.
template<typename SearchFunc> static bool SearchBlueprintWithFunc(const SearchFunc& Func, const UBlueprint* Blueprint, const UEdGraph* LocalGraphScope)
{
// Search the initial blueprint
if (Func(Blueprint))
{
return true;
}
// Optionally walk the asset registry for other blueprints
if (!LocalGraphScope)
{
const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
// Discover additional packages which reference the initial blueprint package name
FARFilter Filter;
AssetRegistryModule.Get().GetReferencers(Blueprint->GetPackage()->GetFName(), Filter.PackageNames, UE::AssetRegistry::EDependencyCategory::Package, UE::AssetRegistry::EDependencyQuery::Hard);
if (Filter.PackageNames.Num() > 0)
{
GWarn->BeginSlowTask(LOCTEXT("LoadingReferencerAssets", "Loading Referencing Assets ..."), true);
{
Filter.TagsAndValues.Add(FBlueprintTags::IsDataOnly, TOptional<FString>(TEXT("false")));
TArray<FAssetData> ReferencerAssetData;
AssetRegistryModule.Get().GetAssets(Filter, ReferencerAssetData);
// For each referencing asset
for (const FAssetData& ReferencerData : ReferencerAssetData)
{
const UObject* AssetReferencer = ReferencerData.GetAsset();
// Conditionally search the asset if it is a blueprint
if (const UBlueprint* BlueprintReferencer = Cast<const UBlueprint>(AssetReferencer))
{
if (BlueprintReferencer && Func(BlueprintReferencer))
{
GWarn->EndSlowTask();
return true;
}
}
// Otherwise check to see if a corresponding world level blueprint is in scope to search
else if (const UWorld* WorldReferencer = Cast<const UWorld>(AssetReferencer))
{
const auto& PersistentLevel = WorldReferencer->PersistentLevel;
if (PersistentLevel && PersistentLevel->OwningWorld && Func(PersistentLevel->GetLevelScriptBlueprint()))
{
GWarn->EndSlowTask();
return true;
}
}
}
}
GWarn->EndSlowTask();
}
}
return false;
}
}
bool FBlueprintEditorUtils::IsVariableUsed(const UBlueprint* Blueprint, const FName& VariableName, const UEdGraph* LocalGraphScope /* = nullptr */)
{
if (VariableName.IsNone())
{
return false;
}
// Retrieve the corresponding variable guid from the blueprint
FGuid VariableGuid;
UBlueprint::GetGuidFromClassByFieldName<FProperty>(Blueprint->SkeletonGeneratedClass, VariableName, VariableGuid);
if (!VariableGuid.IsValid())
{
return false;
}
// Blueprint variable search algorithm
const auto SearchBlueprint = [VariableGuid, VariableName, Blueprint, LocalGraphScope](const UBlueprint* CurrentBlueprint) -> bool
{
TArray<UEdGraph*> AllGraphs;
CurrentBlueprint->GetAllGraphs(AllGraphs);
// For each blueprint subgraph
for (TArray<UEdGraph*>::TConstIterator it(AllGraphs); it; ++it)
{
const UEdGraph* CurrentGraph = *it;
// If the current graph is the specified scope or unbounded
if (CurrentGraph && (CurrentGraph == LocalGraphScope || LocalGraphScope == nullptr))
{
// Check all variable nodes, ignoring connectivity
TArray<UK2Node_Variable*> VariableNodes;
CurrentGraph->GetNodesOfClass(VariableNodes);
if (Algo::AnyOf(VariableNodes, [&VariableGuid, &VariableName](const UK2Node_Variable* VariableNode)
{
return VariableGuid == VariableNode->VariableReference.GetMemberGuid() && VariableName == VariableNode->GetVarName();
}))
{
return true;
}
// Check all GetClassDefaults nodes that exposes the variable as an output pin connected to something
TArray<UK2Node_GetClassDefaults*> ClassDefaultsNodes;
CurrentGraph->GetNodesOfClass(ClassDefaultsNodes);
if (Algo::AnyOf(ClassDefaultsNodes, [&VariableName, &Blueprint](const UK2Node_GetClassDefaults* GraphNode)
{
if (GraphNode->GetInputClass() == Blueprint->SkeletonGeneratedClass)
{
const UEdGraphPin* VarPin = GraphNode->FindPin(VariableName);
if (VarPin && VarPin->Direction == EGPD_Output && VarPin->LinkedTo.Num() > 0)
{
return true;
}
}
return false;
}))
{
return true;
}
// Check for all component bound event nodes. This variable may be referenced by a bound event
// (i.e. "On Component Hit" or any of the delegates you can add from the details panel with a "+" button)
TArray<UK2Node_ComponentBoundEvent*> ComponentBoundEventNodes;
CurrentGraph->GetNodesOfClass(ComponentBoundEventNodes);
if (Algo::AnyOf(ComponentBoundEventNodes, [&VariableName](const UK2Node_ComponentBoundEvent* EventNode)
{
return EventNode->GetComponentPropertyName() == VariableName;
}))
{
return true;
}
// Check all K2Node's which specify private/internal function referencing behavior
TArray<const UK2Node*> GraphNodes;
CurrentGraph->GetNodesOfClass(GraphNodes);
if (Algo::AnyOf(GraphNodes, [&VariableName, &VariableGuid, &Blueprint](const UK2Node* GraphNode)
{
return GraphNode->ReferencesVariable(VariableName, Blueprint->SkeletonGeneratedClass);
}))
{
return true;
}
}
}
return false;
};
// Given the specified variable search algorithm, walk the blueprint asset
return UE::Blueprint::Private::SearchBlueprintWithFunc(SearchBlueprint, Blueprint, LocalGraphScope);
}
bool FBlueprintEditorUtils::IsFunctionUsed(const UBlueprint* Blueprint, const FName& FunctionName, const UEdGraph* LocalGraphScope /* = nullptr */)
{
if (FunctionName.IsNone())
{
return false;
}
// Retrieve the corresponding function guid from the blueprint
FGuid FunctionGuid;
UBlueprint::GetFunctionGuidFromClassByFieldName(Blueprint->SkeletonGeneratedClass, FunctionName, FunctionGuid);
if (!FunctionGuid.IsValid())
{
return false;
}
// Blueprint function search algorithm
const auto SearchBlueprint = [&LocalGraphScope, &FunctionGuid, &FunctionName, &Blueprint](const UBlueprint* CurrentBlueprint) -> bool
{
TArray<UEdGraph*> BlueprintGraphs;
CurrentBlueprint->GetAllGraphs(BlueprintGraphs);
// For each blueprint subgraph
for (TArray<UEdGraph*>::TConstIterator it(BlueprintGraphs); it; ++it)
{
const UEdGraph* CurrentGraph = *it;
// If the current graph is the specified scope or unbounded
if (CurrentGraph && (CurrentGraph == LocalGraphScope || !LocalGraphScope))
{
// Check all function graph nodes, ignoring connectivity
TArray<UK2Node_CallFunction*> CallFunctionNodes;
CurrentGraph->GetNodesOfClass(CallFunctionNodes);
if (Algo::AnyOf(CallFunctionNodes, [&FunctionGuid, &FunctionName](const UK2Node_CallFunction* GraphNode)
{
return FunctionGuid == GraphNode->FunctionReference.GetMemberGuid() && FunctionName == GraphNode->GetFunctionName();
}))
{
return true;
}
// Check all K2Nodes which specify internal function referencing behavior
TArray<const UK2Node*> GraphNodes;
CurrentGraph->GetNodesOfClass(GraphNodes);
if (Algo::AnyOf(GraphNodes, [&FunctionName, &Blueprint](const UK2Node* GraphNode)
{
return GraphNode->ReferencesFunction(FunctionName, Blueprint->SkeletonGeneratedClass);
}))
{
return true;
}
}
}
return false;
};
// Given the specified function search algorithm, walk the blueprint asset
return UE::Blueprint::Private::SearchBlueprintWithFunc(SearchBlueprint, Blueprint, LocalGraphScope);
}
bool FBlueprintEditorUtils::ValidateAllMemberVariables(UBlueprint* InBlueprint, UBlueprint* InParentBlueprint, const FName InVariableName)
{
for(int32 VariableIdx = 0; VariableIdx < InBlueprint->NewVariables.Num(); ++VariableIdx)
{
if(InBlueprint->NewVariables[VariableIdx].VarName == InVariableName)
{
FName NewChildName = FBlueprintEditorUtils::FindUniqueKismetName(InBlueprint, InVariableName.ToString(), InParentBlueprint ? InParentBlueprint->SkeletonGeneratedClass : InBlueprint->ParentClass);
UE_LOG(LogBlueprint, Warning, TEXT("Blueprint %s (child of/implements %s) has a member variable with a conflicting name (%s). Changing to %s."), *InBlueprint->GetName(), *GetNameSafe(InParentBlueprint), *InVariableName.ToString(), *NewChildName.ToString());
FBlueprintEditorUtils::RenameMemberVariable(InBlueprint, InBlueprint->NewVariables[VariableIdx].VarName, NewChildName);
return true;
}
}
return false;
}
bool FBlueprintEditorUtils::ValidateAllComponentMemberVariables(UBlueprint* InBlueprint, UBlueprint* InParentBlueprint, const FName& InVariableName)
{
if(InBlueprint->SimpleConstructionScript != nullptr)
{
TArray<USCS_Node*> ChildSCSNodes = InBlueprint->SimpleConstructionScript->GetAllNodes();
for(int32 NodeIndex = 0; NodeIndex < ChildSCSNodes.Num(); ++NodeIndex)
{
USCS_Node* SCS_Node = ChildSCSNodes[NodeIndex];
if(SCS_Node != nullptr && SCS_Node->GetVariableName() == InVariableName)
{
FName NewChildName = FBlueprintEditorUtils::FindUniqueKismetName(InBlueprint, InVariableName.ToString());
UE_LOG(LogBlueprint, Warning, TEXT("Blueprint %s (child of/implements %s) has a component variable with a conflicting name (%s). Changing to %s."), *InBlueprint->GetName(), *InParentBlueprint->GetName(), *InVariableName.ToString(), *NewChildName.ToString());
FBlueprintEditorUtils::RenameComponentMemberVariable(InBlueprint, SCS_Node, NewChildName);
return true;
}
}
}
return false;
}
bool FBlueprintEditorUtils::ValidateAllTimelines(UBlueprint* InBlueprint, UBlueprint* InParentBlueprint, const FName& InVariableName)
{
for (int32 TimelineIndex=0; TimelineIndex < InBlueprint->Timelines.Num(); ++TimelineIndex)
{
UTimelineTemplate* TimelineTemplate = InBlueprint->Timelines[TimelineIndex];
if( TimelineTemplate )
{
if( TimelineTemplate->GetFName() == InVariableName )
{
FName NewName = FBlueprintEditorUtils::FindUniqueKismetName(InBlueprint, TimelineTemplate->GetName());
FBlueprintEditorUtils::RenameTimeline(InBlueprint, TimelineTemplate->GetFName(), NewName);
UE_LOG(LogBlueprint, Warning, TEXT("Blueprint %s (child of/implements %s) has a timeline with a conflicting name (%s). Changing to %s."), *InBlueprint->GetName(), *InParentBlueprint->GetName(), *InVariableName.ToString(), *NewName.ToString());
return true;
}
}
}
return false;
}
bool FBlueprintEditorUtils::ValidateAllFunctionGraphs(UBlueprint* InBlueprint, UBlueprint* InParentBlueprint, const FName& InVariableName)
{
for (int32 FunctionIndex=0; FunctionIndex < InBlueprint->FunctionGraphs.Num(); ++FunctionIndex)
{
UEdGraph* FunctionGraph = InBlueprint->FunctionGraphs[FunctionIndex];
if( FunctionGraph->GetFName() == InVariableName )
{
FName NewName = FBlueprintEditorUtils::FindUniqueKismetName(InBlueprint, FunctionGraph->GetName());
FBlueprintEditorUtils::RenameGraph(FunctionGraph, NewName.ToString());
UE_LOG(LogBlueprint, Warning, TEXT("Blueprint %s (child of/implements %s) has a function graph with a conflicting name (%s). Changing to %s."), *InBlueprint->GetName(), *InParentBlueprint->GetName(), *InVariableName.ToString(), *NewName.ToString());
return true;
}
}
return false;
}
void FBlueprintEditorUtils::FixupVariableDescription(UBlueprint* Blueprint, FBPVariableDescription& VarDesc)
{
if ((VarDesc.PropertyFlags & CPF_Config) != 0 && Blueprint->GetLinkerCustomVersion(FBlueprintsObjectVersion::GUID) < FBlueprintsObjectVersion::DisallowObjectConfigVars)
{
// Synchronized with FBlueprintVarActionDetails::IsConfigCheckBoxEnabled
const FEdGraphPinType& VarType = VarDesc.VarType;
if (VarType.PinCategory == UEdGraphSchema_K2::PC_Object ||
VarType.PinCategory == UEdGraphSchema_K2::PC_Interface)
{
VarDesc.PropertyFlags &= ~CPF_Config;
}
}
// Remove bitflag enum type metadata if the enum type name is missing or if the enum type is no longer a bitflags type.
if (VarDesc.HasMetaData(FBlueprintMetadata::MD_BitmaskEnum))
{
FString BitmaskEnumTypePath = VarDesc.GetMetaData(FBlueprintMetadata::MD_BitmaskEnum);
if (!BitmaskEnumTypePath.IsEmpty())
{
const UEnum* BitflagsEnum = nullptr;
// if the enum is saved by name (deprecated), find the associated enum and reserialize it as a long asset path
if (FPackageName::IsShortPackageName(BitmaskEnumTypePath))
{
BitflagsEnum = FindFirstObject<UEnum>(GetData(BitmaskEnumTypePath));
if (BitflagsEnum != nullptr)
{
BitmaskEnumTypePath = FTopLevelAssetPath(BitflagsEnum->GetPackage()->GetFName(), BitflagsEnum->GetFName()).ToString();
VarDesc.SetMetaData(FBlueprintMetadata::MD_BitmaskEnum, BitmaskEnumTypePath);
}
else
{
UE_LOG(LogBlueprint, Error, TEXT("Enum %s cannot be loaded"), *BitmaskEnumTypePath);
}
}
else
{
BitflagsEnum = FindObject<UEnum>(nullptr, GetData(BitmaskEnumTypePath));
}
if (BitflagsEnum == nullptr || !BitflagsEnum->HasMetaData(*FBlueprintMetadata::MD_Bitflags.ToString()) || !UEdGraphSchema_K2::IsAllowableBlueprintVariableType(BitflagsEnum))
{
VarDesc.RemoveMetaData(FBlueprintMetadata::MD_BitmaskEnum);
}
}
else
{
VarDesc.RemoveMetaData(FBlueprintMetadata::MD_BitmaskEnum);
}
}
}
void FBlueprintEditorUtils::ValidateBlueprintChildVariables(UBlueprint* InBlueprint, const FName InVariableName,
TFunction<void(UBlueprint* InChildBP, const FName InVariableName, bool bValidatedVariable)> PostValidationCallback)
{
// Iterate over currently-loaded Blueprints and potentially adjust their variable names if they conflict with the parent
for(TObjectIterator<UBlueprint> BlueprintIt; BlueprintIt; ++BlueprintIt)
{
UBlueprint* ChildBP = *BlueprintIt;
if(ChildBP != nullptr && ChildBP->ParentClass != nullptr)
{
TArray<UBlueprint*> ParentBPArray;
// Get the parent hierarchy
UBlueprint::GetBlueprintHierarchyFromClass(ChildBP->ParentClass, ParentBPArray);
// Also get any BP interfaces we use
TArray<UClass*> ImplementedInterfaces;
FindImplementedInterfaces(ChildBP, true, ImplementedInterfaces);
for (UClass* ImplementedInterface : ImplementedInterfaces)
{
UBlueprint* BlueprintInterfaceClass = UBlueprint::GetBlueprintFromClass(ImplementedInterface);
if(BlueprintInterfaceClass != nullptr)
{
ParentBPArray.Add(BlueprintInterfaceClass);
}
}
if(ParentBPArray.Contains(InBlueprint))
{
bool bValidatedVariable = false;
bValidatedVariable = ValidateAllMemberVariables(ChildBP, InBlueprint, InVariableName);
if(!bValidatedVariable)
{
bValidatedVariable = ValidateAllComponentMemberVariables(ChildBP, InBlueprint, InVariableName);
}
if(!bValidatedVariable)
{
bValidatedVariable = ValidateAllTimelines(ChildBP, InBlueprint, InVariableName);
}
if(!bValidatedVariable)
{
bValidatedVariable = ValidateAllFunctionGraphs(ChildBP, InBlueprint, InVariableName);
}
if (PostValidationCallback)
{
// Perform custom post-validation (if specified).
PostValidationCallback(ChildBP, InVariableName, bValidatedVariable);
}
}
}
}
}
int32 FBlueprintEditorUtils::GetChildrenOfBlueprint(UBlueprint* InBlueprint, TArray<FAssetData>& OutChildren, bool bInRecursive /*= true*/)
{
int32 Count = 0;
const FAssetData ParentAsset(InBlueprint);
TArray<FName> ParentNames;
ParentNames.Add(ParentAsset.GetTagValueRef<FName>(FBlueprintTags::GeneratedClassPath));
for (int32 ParentIdx = 0; ParentIdx < ParentNames.Num(); ++ParentIdx)
{
FARFilter Filter;
Filter.TagsAndValues.Add(FBlueprintTags::ParentClassPath, ParentNames[ParentIdx].ToString());
TArray<FAssetData> FoundAssets;
if (FAssetRegistryModule::GetRegistry().GetAssets(Filter, FoundAssets) && FoundAssets.Num() > 0)
{
if (bInRecursive)
{
for (const FAssetData& Child : FoundAssets)
{
ParentNames.Add(Child.GetTagValueRef<FName>(FBlueprintTags::GeneratedClassPath));
}
}
Count += FoundAssets.Num();
OutChildren.Append(MoveTemp(FoundAssets));
}
}
return Count;
}
void FBlueprintEditorUtils::MarkBlueprintChildrenAsModified(UBlueprint* InBlueprint)
{
TArray<FAssetData> Children;
if (GetChildrenOfBlueprint(InBlueprint, Children) > 0)
{
SIZE_T Unloaded = Algo::CountIf(Children,
[](const FAssetData& Asset)
{
return !Asset.IsAssetLoaded();
});
// If there are any unloaded children, ask the user to verify
EAppReturnType::Type DialogResponse = EAppReturnType::Yes;
if (Unloaded > 0)
{
FText Message = FText::Format(LOCTEXT("LoadChildrenPopupMessage", "Load {0} unloaded child blueprints to fix up phantom references?"), FText::FromString(LexToString(Unloaded)));
FText Title = LOCTEXT("LoadChildrenPopupTitle", "Load Unloaded Children?");
DialogResponse = FMessageDialog::Open(EAppMsgType::YesNo, Message, Title);
}
// Conditionally Load Children and mark as modified
const bool bLoad = (DialogResponse == EAppReturnType::Yes);
for (FAssetData& Child : Children)
{
if (UBlueprint* ChildBlueprint = Cast<UBlueprint>(Child.FastGetAsset(bLoad)))
{
MarkBlueprintAsModified(ChildBlueprint);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
/** Shared function for posting notification toasts */
static void ShowNotification(const FText& Message, EMessageSeverity::Type Severity)
{
if(FApp::IsUnattended())
{
switch(Severity)
{
case EMessageSeverity::Error:
UE_LOG(LogBlueprint, Error, TEXT("%s"), *Message.ToString());
break;
case EMessageSeverity::PerformanceWarning:
case EMessageSeverity::Warning:
UE_LOG(LogBlueprint, Warning, TEXT("%s"), *Message.ToString());
break;
case EMessageSeverity::Info:
UE_LOG(LogBlueprint, Log, TEXT("%s"), *Message.ToString());
break;
}
}
else
{
FNotificationInfo Warning(Message);
Warning.ExpireDuration = 5.0f;
Warning.bFireAndForget = true;
switch(Severity)
{
case EMessageSeverity::Error:
Warning.Image = FCoreStyle::Get().GetBrush(TEXT("MessageLog.Error"));
break;
case EMessageSeverity::PerformanceWarning:
case EMessageSeverity::Warning:
Warning.Image = FCoreStyle::Get().GetBrush(TEXT("MessageLog.Warning"));
break;
case EMessageSeverity::Info:
Warning.Image = FCoreStyle::Get().GetBrush(TEXT("MessageLog.Info"));
break;
}
FSlateNotificationManager::Get().AddNotification(Warning);
}
}
//////////////////////////////////////////////////////////////////////////
// Interfaces
FGuid FBlueprintEditorUtils::FindInterfaceGraphGuid(const FName& GraphName, const UClass* InterfaceClass)
{
// check if this is a blueprint - only blueprint interfaces can have Guids
check(InterfaceClass);
const UBlueprint* InterfaceBlueprint = Cast<UBlueprint>(InterfaceClass->ClassGeneratedBy);
if(InterfaceBlueprint != nullptr)
{
// find the graph for this function
TArray<UEdGraph*> InterfaceGraphs;
InterfaceBlueprint->GetAllGraphs(InterfaceGraphs);
for (const UEdGraph* InterfaceGraph : InterfaceGraphs)
{
if(InterfaceGraph != nullptr && InterfaceGraph->GetFName() == GraphName)
{
return InterfaceGraph->GraphGuid;
}
}
}
return FGuid();
}
FGuid FBlueprintEditorUtils::FindInterfaceFunctionGuid(const UFunction* Function, const UClass* InterfaceClass)
{
check(Function);
return FindInterfaceGraphGuid(Function->GetFName(), InterfaceClass);
}
// Add a new interface, and member function graphs to the blueprint
bool FBlueprintEditorUtils::ImplementNewInterface(UBlueprint* Blueprint, FTopLevelAssetPath InterfaceClassPathName)
{
check(!InterfaceClassPathName.IsNull());
// Attempt to find the class we want to implement
UClass* InterfaceClass = FindObject<UClass>(InterfaceClassPathName);
// Make sure the class is found, and isn't native (since Blueprints don't necessarily generate native classes.
check(InterfaceClass);
// Check to make sure we haven't already implemented it
for( int32 i = 0; i < Blueprint->ImplementedInterfaces.Num(); i++ )
{
if( Blueprint->ImplementedInterfaces[i].Interface == InterfaceClass )
{
ShowNotification(
FText::Format(
LOCTEXT("InterfaceAlreadyImplementedFmt", "Blueprint '{0}' already implements the interface '{1}'"),
FText::FromString(Blueprint->GetName()),
FText::FromString(InterfaceClassPathName.ToString())
),
EMessageSeverity::Warning
);
return false;
}
}
// Make a new entry for this interface
FBPInterfaceDescription NewInterface;
NewInterface.Interface = InterfaceClass;
bool bAllFunctionsAdded = true;
// Add the graphs for the functions required by this interface
for( TFieldIterator<UFunction> FunctionIter(InterfaceClass, EFieldIteratorFlags::IncludeSuper); FunctionIter; ++FunctionIter )
{
UFunction* Function = *FunctionIter;
const bool bIsAnimFunction = Function->HasMetaData(FBlueprintMetadata::MD_AnimBlueprintFunction);
if(bIsAnimFunction && !Blueprint->IsA<UAnimBlueprint>())
{
bAllFunctionsAdded = false;
ShowNotification(
FText::Format(
LOCTEXT("AddedAnimFunctionToNonAnimBlueprint", "Blueprint '{0}' is not an animation blueprint and therefore cannot implement interface '{1}'"),
FText::FromString(Blueprint->GetName()),
FText::FromString(InterfaceClassPathName.ToString())
),
EMessageSeverity::Error
);
break;
}
if( (UEdGraphSchema_K2::CanKismetOverrideFunction(Function) && !UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(Function)) ||
bIsAnimFunction)
{
FName FunctionName = Function->GetFName();
UEdGraph* FuncGraph = FindObject<UEdGraph>( Blueprint, *(FunctionName.ToString()) );
if (FuncGraph != nullptr)
{
bAllFunctionsAdded = false;
ShowNotification(
FText::Format(
LOCTEXT("InterfaceFunctionConflictsFmt", "Blueprint '{0}' has a function or graph which conflicts with function '{1}' in interface '{2}'"),
FText::FromString(Blueprint->GetName()),
FText::FromName(FunctionName),
FText::FromString(InterfaceClassPathName.ToString())
),
EMessageSeverity::Error
);
break;
}
UEdGraph* NewGraph;
if(bIsAnimFunction)
{
NewGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, FunctionName, UAnimationGraph::StaticClass(), UAnimationGraphSchema::StaticClass());
}
else
{
NewGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, FunctionName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
}
NewGraph->bAllowDeletion = false;
NewGraph->InterfaceGuid = FindInterfaceFunctionGuid(Function, InterfaceClass);
NewInterface.Graphs.Add(NewGraph);
FBlueprintEditorUtils::AddInterfaceGraph(Blueprint, NewGraph, InterfaceClass);
}
}
if (bAllFunctionsAdded)
{
Blueprint->ImplementedInterfaces.Add(NewInterface);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
return bAllFunctionsAdded;
}
bool FBlueprintEditorUtils::ImplementNewInterface(UBlueprint* Blueprint, const FName& InterfaceClassName)
{
FTopLevelAssetPath InterfaceClassPathName = UClass::TryConvertShortTypeNameToPathName<UStruct>(InterfaceClassName.ToString(), ELogVerbosity::Warning, TEXT("FBlueprintEditorUtils::ImplementNewInterface"));
return ImplementNewInterface(Blueprint, InterfaceClassPathName);
}
// Gets the graphs currently in the blueprint associated with the specified interface
void FBlueprintEditorUtils::GetInterfaceGraphs(UBlueprint* Blueprint, FTopLevelAssetPath InterfaceClassPathName, TArray<UEdGraph*>& ChildGraphs)
{
ChildGraphs.Empty();
if (InterfaceClassPathName.IsNull())
{
return;
}
// Find the implemented interface
for (const FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
if (InterfaceDesc.Interface && InterfaceDesc.Interface->GetClassPathName() == InterfaceClassPathName)
{
ChildGraphs = InterfaceDesc.Graphs;
return;
}
}
}
void FBlueprintEditorUtils::GetInterfaceGraphs(UBlueprint* Blueprint, const FName& InterfaceClassName, TArray<UEdGraph*>& ChildGraphs)
{
FTopLevelAssetPath InterfaceClassPathName = UClass::TryConvertShortTypeNameToPathName<UStruct>(InterfaceClassName.ToString(), ELogVerbosity::Warning, TEXT("FBlueprintEditorUtils::GetInterfaceGraphs"));
GetInterfaceGraphs(Blueprint, InterfaceClassPathName, ChildGraphs);
}
UFunction* FBlueprintEditorUtils::GetInterfaceFunction(UBlueprint* Blueprint, const FName FuncName)
{
UFunction* Function = nullptr;
// If that class is an interface class implemented by this function, then return true
for (const FBPInterfaceDescription& I : Blueprint->ImplementedInterfaces)
{
if (I.Interface)
{
Function = FindUField<UFunction>(I.Interface, FuncName);
if (Function)
{
// found it, done
return Function;
}
}
}
// Check if it is in a native class or parent class
for (UClass* TempClass = Blueprint->ParentClass; (nullptr != TempClass) && (nullptr == Function); TempClass = TempClass->GetSuperClass())
{
for (const FImplementedInterface& I : TempClass->Interfaces)
{
Function = FindUField<UFunction>(I.Class, FuncName);
if (Function)
{
// found it, done
return Function;
}
}
}
return nullptr;
}
bool FBlueprintEditorUtils::IsInterfaceFunction(UBlueprint* Blueprint, UFunction* Function)
{
if (Blueprint == nullptr || Function == nullptr)
{
return false;
}
const FName FuncName = Function->GetFName();
// Will return nullptr if the function isn't found
return (GetInterfaceFunction(Blueprint, FuncName) != nullptr);
}
// Remove an implemented interface, and its associated member function graphs
void FBlueprintEditorUtils::RemoveInterface(UBlueprint* Blueprint, FTopLevelAssetPath InterfaceClassPathName, bool bPreserveFunctions /*= false*/)
{
if (InterfaceClassPathName.IsNull())
{
return;
}
// Find the implemented interface
int32 Idx = INDEX_NONE;
for (int32 i = 0; i < Blueprint->ImplementedInterfaces.Num(); i++)
{
const FBPInterfaceDescription& InterfaceDesc = Blueprint->ImplementedInterfaces[i];
if (InterfaceDesc.Interface && InterfaceDesc.Interface->GetClassPathName() == InterfaceClassPathName)
{
Idx = i;
break;
}
}
if (ensureMsgf(Idx != INDEX_NONE, TEXT("%s: No implementation was found for \'%s\'."), *Blueprint->GetName(), *InterfaceClassPathName.ToString()))
{
FBPInterfaceDescription& CurrentInterface = Blueprint->ImplementedInterfaces[Idx];
const UClass* InterfaceClass = Blueprint->ImplementedInterfaces[Idx].Interface;
const FScopedTransaction Transaction(LOCTEXT("RemoveInterface", "Remove interface"));
Blueprint->Modify();
// For every function and event in the interface...
for (TFieldIterator<UFunction> FunctionIt(InterfaceClass); FunctionIt; ++FunctionIt)
{
UFunction* Function = *FunctionIt;
const FName FunctionName = Function->GetFName();
// If this function name is in the list of interface graphs, then handle it like a function
if (FunctionName == UEdGraphSchema_K2::FN_ExecuteUbergraphBase ||
FBlueprintEditorUtils::RemoveInterfaceFunction(Blueprint, CurrentInterface, Function, bPreserveFunctions))
{
continue;
}
// Find all events placed in the event graph so we can check if they belong to this interface
TArray<UK2Node_Event*> AllEvents;
FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, AllEvents);
for (TArray<UK2Node_Event*>::TIterator NodeIt(AllEvents); NodeIt; ++NodeIt)
{
UK2Node_Event* EventNode = *NodeIt;
if (EventNode->EventReference.GetMemberParentClass(EventNode->GetBlueprintClassFromNode()) == InterfaceClass)
{
UEdGraph* EventNodeGraph = EventNode->GetGraph();
check(EventNodeGraph);
EventNodeGraph->Modify();
if (bPreserveFunctions)
{
// Create a custom event with the same name and signature
const FVector2D PreviousNodePos = FVector2D(EventNode->NodePosX, EventNode->NodePosY);
const FString PreviousNodeName = EventNode->EventReference.GetMemberName().ToString();
const UFunction* PreviousSignatureFunction = EventNode->FindEventSignatureFunction();
check(PreviousSignatureFunction);
UK2Node_CustomEvent* NewEvent = UK2Node_CustomEvent::CreateFromFunction(PreviousNodePos, EventNode->GetGraph(), PreviousNodeName, PreviousSignatureFunction, false);
// Move the pin links from the old pin to the new pin to preserve connections
for (UEdGraphPin* CurrentPin : EventNode->Pins)
{
UEdGraphPin* TargetPin = NewEvent->FindPinChecked(CurrentPin->PinName);
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
Schema->MovePinLinks(*CurrentPin, *TargetPin);
}
}
EventNodeGraph->RemoveNode(EventNode);
break;
}
}
}
// Then remove the interface from the list
Blueprint->ImplementedInterfaces.RemoveAt(Idx, 1);
// Refresh all the nodes to make sure that the references to "Self" are updated appropriately on any function calls @see UE-78253
FBlueprintEditorUtils::RefreshAllNodes(Blueprint);
// Now recompile the blueprint (this needs to be done outside of RemoveGraph, after it's been removed from ImplementedInterfaces - otherwise it'll re-add it)
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
// Mark Child Blueprints as modified to fixup references to the Interface
MarkBlueprintChildrenAsModified(Blueprint);
}
}
void FBlueprintEditorUtils::RemoveInterface(UBlueprint* Blueprint, const FName& InterfaceClassName, bool bPreserveFunctions /*= false*/)
{
FTopLevelAssetPath InterfaceClassPathName = UClass::TryConvertShortTypeNameToPathName<UStruct>(InterfaceClassName.ToString(), ELogVerbosity::Warning, TEXT("FBlueprintEditorUtils::RemoveInterface"));
return RemoveInterface(Blueprint, InterfaceClassPathName, bPreserveFunctions);
}
bool FBlueprintEditorUtils::RemoveInterfaceFunction(UBlueprint* Blueprint, FBPInterfaceDescription& Interface, UFunction* Function, bool bPreserveFunction)
{
for (auto it = Interface.Graphs.CreateIterator(); it; ++it)
{
UEdGraph* CurrentGraph = *it;
if (Function->GetFName() == CurrentGraph->GetFName())
{
CurrentGraph->Modify();
Blueprint->Modify();
if (bPreserveFunction)
{
// Promote the graph if the user wants to preserve it:
PromoteGraphFromInterfaceOverride(Blueprint, CurrentGraph);
Blueprint->FunctionGraphs.Add(CurrentGraph);
}
FBlueprintEditorUtils::UpdateTransactionalFlags(Blueprint);
if(!bPreserveFunction)
{
// Remove the interface graph
FBlueprintEditorUtils::RemoveGraph(Blueprint, CurrentGraph, EGraphRemoveFlags::MarkTransient); // Do not recompile, yet
}
return true;
}
}
return false;
}
void FBlueprintEditorUtils::PromoteGraphFromInterfaceOverride(UBlueprint* InBlueprint, UEdGraph* InInterfaceGraph)
{
InInterfaceGraph->bAllowDeletion = true;
InInterfaceGraph->bAllowRenaming = true;
InInterfaceGraph->bEditable = true;
InInterfaceGraph->InterfaceGuid.Invalidate();
// We need to flag the entry node to make sure that the compiled function is callable
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
Schema->AddExtraFunctionFlags(InInterfaceGraph, (FUNC_BlueprintCallable | FUNC_BlueprintEvent | FUNC_Public));
Schema->MarkFunctionEntryAsEditable(InInterfaceGraph, true);
// Move all non-exec pins from the function entry node to being user defined pins
TArray<UK2Node_FunctionEntry*> FunctionEntryNodes;
InInterfaceGraph->GetNodesOfClass(FunctionEntryNodes);
if (FunctionEntryNodes.Num() > 0)
{
UK2Node_FunctionEntry* FunctionEntry = FunctionEntryNodes[0];
FunctionEntry->PromoteFromInterfaceOverride();
}
// Move all non-exec pins from the function result node to being user defined pins
TArray<UK2Node_FunctionResult*> FunctionResultNodes;
InInterfaceGraph->GetNodesOfClass(FunctionResultNodes);
if (FunctionResultNodes.Num() > 0)
{
UK2Node_FunctionResult* PrimaryFunctionResult = FunctionResultNodes[0];
PrimaryFunctionResult->PromoteFromInterfaceOverride();
// Reconstruct all result nodes so they update their pins accordingly
for (UK2Node_FunctionResult* FunctionResult : FunctionResultNodes)
{
if (PrimaryFunctionResult != FunctionResult)
{
FunctionResult->PromoteFromInterfaceOverride(false);
}
}
}
// Promote any animation linked input poses
TArray<UAnimGraphNode_LinkedInputPose*> LinkedInputPoseNodes;
InInterfaceGraph->GetNodesOfClass(LinkedInputPoseNodes);
for (UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode : LinkedInputPoseNodes)
{
LinkedInputPoseNode->PromoteFromInterfaceOverride();
}
}
void FBlueprintEditorUtils::CleanNullGraphReferencesRecursive(UEdGraph* Graph)
{
for (int32 GraphIndex = 0; GraphIndex < Graph->SubGraphs.Num(); )
{
if (UEdGraph* ChildGraph = Graph->SubGraphs[GraphIndex])
{
CleanNullGraphReferencesRecursive(ChildGraph);
++GraphIndex;
}
else
{
UE_LOG(LogBlueprint, Warning, TEXT("Found NULL graph reference in children of '%s', removing it!"), *Graph->GetPathName());
Graph->SubGraphs.RemoveAt(GraphIndex);
}
}
}
void FBlueprintEditorUtils::CleanNullGraphReferencesInArray(UBlueprint* Blueprint, TArray<UEdGraph*>& GraphArray)
{
for (int32 GraphIndex = 0; GraphIndex < GraphArray.Num(); )
{
if (UEdGraph* Graph = GraphArray[GraphIndex])
{
CleanNullGraphReferencesRecursive(Graph);
++GraphIndex;
}
else
{
UE_LOG(LogBlueprint, Warning, TEXT("Found NULL graph reference in '%s', removing it!"), *Blueprint->GetPathName());
GraphArray.RemoveAt(GraphIndex);
}
}
}
void FBlueprintEditorUtils::PurgeNullGraphs(UBlueprint* Blueprint)
{
CleanNullGraphReferencesInArray(Blueprint, MutableView(Blueprint->UbergraphPages));
CleanNullGraphReferencesInArray(Blueprint, MutableView(Blueprint->FunctionGraphs));
CleanNullGraphReferencesInArray(Blueprint, MutableView(Blueprint->DelegateSignatureGraphs));
CleanNullGraphReferencesInArray(Blueprint, MutableView(Blueprint->MacroGraphs));
}
struct FConformCallsToParentFunctionUtils
{
// Remove a parent function call node without breaking existing connections
static void RemoveParentFunctionCallNode(UBlueprint* InBlueprint, UK2Node_CallParentFunction* InCallFunctionNode)
{
// Cache a reference to the output exec pin
UEdGraphPin* OutputPin = InCallFunctionNode->GetThenPin();
// We're going to destroy the existing parent function call node, but first we need to persist any existing connections
for (int PinIndex = 0; PinIndex < InCallFunctionNode->Pins.Num(); ++PinIndex)
{
UEdGraphPin* InputPin = InCallFunctionNode->Pins[PinIndex];
check(nullptr != InputPin);
// If this is an input exec pin
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
if (K2Schema && K2Schema->IsExecPin(*InputPin) && InputPin->Direction == EGPD_Input)
{
// Redirect any existing connections to the input exec pin to whatever pin(s) the output exec pin is connected to
for (int InputLinkedToPinIndex = 0; InputLinkedToPinIndex < InputPin->LinkedTo.Num(); ++InputLinkedToPinIndex)
{
UEdGraphPin* InputLinkedToPin = InputPin->LinkedTo[InputLinkedToPinIndex];
check(nullptr != InputLinkedToPin);
// Break the existing link to the node we're about to remove
InputLinkedToPin->BreakLinkTo(InputPin);
// Redirect the input connection to the output connection(s)
for (int OutputLinkedToPinIndex = 0; OutputLinkedToPinIndex < OutputPin->LinkedTo.Num(); ++OutputLinkedToPinIndex)
{
UEdGraphPin* OutputLinkedToPin = OutputPin->LinkedTo[OutputLinkedToPinIndex];
check(nullptr != OutputLinkedToPin);
// Make sure the output connection isn't linked to the node we're about to remove
if (OutputLinkedToPin->LinkedTo.Contains(OutputPin))
{
OutputLinkedToPin->BreakLinkTo(OutputPin);
}
// Fix up the connection
InputLinkedToPin->MakeLinkTo(OutputLinkedToPin);
}
}
}
}
// Emit something to the log to indicate that we're making a change
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), InCallFunctionNode->GetNodeTitle(ENodeTitleType::ListView));
Args.Add(TEXT("FunctionNodeName"), FText::FromString(InCallFunctionNode->GetName()));
InBlueprint->Message_Note(FText::Format(LOCTEXT("CallParentNodeRemoved_Note", "{NodeTitle} ({FunctionNodeName}) was not valid for this Blueprint - it has been removed."), Args).ToString());
// Destroy the existing parent function call node (this will also break pin links and remove it from the graph)
InCallFunctionNode->DestroyNode();
}
// Makes sure that all function overrides are valid, and replaces with local functions if not
static void ConformParentFunctionOverrides(UBlueprint* InBlueprint)
{
TArray<UEdGraph*> AllGraphs;
InBlueprint->GetAllGraphs(AllGraphs);
for (UEdGraph* CurrentGraph : AllGraphs)
{
check(CurrentGraph);
TArray<UK2Node_FunctionEntry*> FunctionEntryNodes;
CurrentGraph->GetNodesOfClass<UK2Node_FunctionEntry>(FunctionEntryNodes);
for (UK2Node_FunctionEntry* FunctionEntryNode : FunctionEntryNodes)
{
check(FunctionEntryNode);
if (!FunctionEntryNode->FunctionReference.IsSelfContext())
{
UClass* SignatureClass = FunctionEntryNode->FunctionReference.GetMemberParentClass();
if (const UBlueprint* SignatureClassBlueprint = UBlueprint::GetBlueprintFromClass(SignatureClass))
{
// Redirect to the skeleton class for Blueprint types.
SignatureClass = SignatureClassBlueprint->SkeletonGeneratedClass;
}
if (SignatureClass)
{
const UFunction* Function = FunctionEntryNode->FunctionReference.ResolveMember<UFunction>(SignatureClass);
if (Function == nullptr)
{
// Remove any calls to the parent class implementation
TArray<UK2Node_CallParentFunction*> CallFunctionNodes;
CurrentGraph->GetNodesOfClass<UK2Node_CallParentFunction>(CallFunctionNodes);
for (UK2Node_CallParentFunction* CallFunctionNode : CallFunctionNodes)
{
RemoveParentFunctionCallNode(InBlueprint, CallFunctionNode);
}
// Execute the same conversion path we use for interface graphs
FBlueprintEditorUtils::PromoteGraphFromInterfaceOverride(InBlueprint, CurrentGraph);
// Emit something to the log to indicate that we've made a change
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), FunctionEntryNode->GetNodeTitle(ENodeTitleType::ListView));
Args.Add(TEXT("ParentClass"), FText::FromString(SignatureClass->GetName()));
InBlueprint->Message_Note(FText::Format(LOCTEXT("ConvertedToLocalMemberFunction_Note", "Function '{NodeTitle}' was previously implemented as an override, but the function is no longer found in class '{ParentClass}'. As a result, it has been converted to a full member function."), Args).ToString());
}
else
{
if (FunctionEntryNode->bEnforceConstCorrectness)
{
// Sync the 'const' attribute with the original function, in case it has been changed
const bool bIsConstFunction = Function->HasAllFunctionFlags(FUNC_Const);
if (bIsConstFunction != FunctionEntryNode->HasAllExtraFlags(FUNC_Const))
{
int32 ExtraFlags = FunctionEntryNode->GetExtraFlags();
FunctionEntryNode->Modify();
FunctionEntryNode->SetExtraFlags(ExtraFlags ^ FUNC_Const);
}
}
}
}
}
// Rename the graph if it does not match the actual function name.
const FName FunctionName = (FunctionEntryNode->CustomGeneratedFunctionName != NAME_None) ? FunctionEntryNode->CustomGeneratedFunctionName : FunctionEntryNode->FunctionReference.GetMemberName();
if (FunctionEntryNode == FunctionEntryNodes[0]
&& !FBlueprintEditorUtils::IsEventGraph(CurrentGraph)
&& CurrentGraph->GetFName() != FunctionName)
{
FBlueprintEditorUtils::RenameGraph(CurrentGraph, FunctionName.ToString());
}
}
}
}
};
// Makes sure that calls to parent functions are valid, and removes them if not
void FBlueprintEditorUtils::ConformCallsToParentFunctions(UBlueprint* Blueprint)
{
check(nullptr != Blueprint);
// First, ensure that all function override implementations are up-to-date.
FConformCallsToParentFunctionUtils::ConformParentFunctionOverrides(Blueprint);
// Get the Blueprint's parent class.
UClass* ParentClass = Blueprint->ParentClass;
if (const UBlueprint* ParentBlueprint = UBlueprint::GetBlueprintFromClass(ParentClass))
{
// Defer to the skeleton class, as a Blueprint parent may not have been fully compiled yet (e.g. after a rename).
ParentClass = ParentBlueprint->SkeletonGeneratedClass;
}
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
for(int GraphIndex = 0; GraphIndex < AllGraphs.Num(); ++GraphIndex)
{
UEdGraph* CurrentGraph = AllGraphs[GraphIndex];
check(nullptr != CurrentGraph);
// Make sure the graph is loaded
if(!CurrentGraph->HasAnyFlags(RF_NeedLoad|RF_NeedPostLoad))
{
TArray<UK2Node_CallParentFunction*> CallFunctionNodes;
CurrentGraph->GetNodesOfClass<UK2Node_CallParentFunction>(CallFunctionNodes);
// For each parent function call node in the graph
for(int CallFunctionNodeIndex = 0; CallFunctionNodeIndex < CallFunctionNodes.Num(); ++CallFunctionNodeIndex)
{
UK2Node_CallParentFunction* CallFunctionNode = CallFunctionNodes[CallFunctionNodeIndex];
check(nullptr != CallFunctionNode);
// Make sure the node has already been loaded
if(!CallFunctionNode->HasAnyFlags(RF_NeedLoad|RF_NeedPostLoad))
{
// Attempt to locate the function within the parent class
FName MemberName = CallFunctionNode->FunctionReference.GetMemberName();
const UFunction* TargetFunction = ParentClass ? ParentClass->FindFunctionByName(MemberName) : nullptr;
if (TargetFunction == nullptr && ParentClass != nullptr)
{
// In case the function was renamed in the parent class, try to look up using the member's GUID.
const FGuid MemberGuid = CallFunctionNode->FunctionReference.GetMemberGuid();
if (MemberGuid.IsValid())
{
MemberName = UBlueprint::GetFieldNameFromClassByGuid<UFunction>(ParentClass, MemberGuid);
if (MemberName != NAME_None)
{
TargetFunction = ParentClass->FindFunctionByName(MemberName);
}
}
}
if(TargetFunction != nullptr)
{
// Check to see if the function signature does not match the (authoritative) parent class.
if(TargetFunction->GetOwnerClass()->GetAuthoritativeClass() != CallFunctionNode->FunctionReference.GetMemberParentClass(ParentClass->GetAuthoritativeClass()))
{
// Emit something to the log to indicate that we're making a change
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), CallFunctionNode->GetNodeTitle(ENodeTitleType::ListView));
Args.Add(TEXT("FunctionNodeName"), FText::FromString(CallFunctionNode->GetName()));
Blueprint->Message_Note(FText::Format(LOCTEXT("CallParentFunctionSignatureFixed_Note", "{NodeTitle} ({FunctionNodeName}) had an invalid function signature - it has now been fixed."), Args).ToString() );
// Redirect to the correct parent function. Note that for Blueprints, internally this will switch to the target function that's owned by the authoritative parent class.
CallFunctionNode->SetFromFunction(TargetFunction);
}
}
else
{
// Remove the parent function call node, preserving any existing connections.
FConformCallsToParentFunctionUtils::RemoveParentFunctionCallNode(Blueprint, CallFunctionNode);
}
}
}
}
}
}
namespace
{
static bool ExtendedIsParent(const UClass* Parent, const UClass* Child)
{
if (Parent && Child)
{
if (Child->IsChildOf(Parent))
{
return true;
}
if (Parent->ClassGeneratedBy)
{
if (Parent->ClassGeneratedBy == Child->ClassGeneratedBy)
{
return true;
}
if (const UBlueprint* ParentBP = Cast<UBlueprint>(Parent->ClassGeneratedBy))
{
if (ParentBP->SkeletonGeneratedClass && Child->IsChildOf(ParentBP->SkeletonGeneratedClass))
{
return true;
}
if (ParentBP->GeneratedClass && Child->IsChildOf(ParentBP->GeneratedClass))
{
return true;
}
}
}
}
return false;
}
static void FixOverriddenEventSignature(UK2Node_Event* EventNode, UBlueprint* Blueprint, UEdGraph* CurrentGraph)
{
check(EventNode && Blueprint && CurrentGraph);
UClass* CurrentClass = EventNode->GetBlueprintClassFromNode();
FMemberReference& FuncRef = EventNode->EventReference;
const FName EventFuncName = FuncRef.GetMemberName();
ensure(EventFuncName != NAME_None);
ensure(!EventNode->IsA<UK2Node_CustomEvent>());
const UFunction* TargetFunction = FuncRef.ResolveMember<UFunction>(CurrentClass);
const UClass* FuncOwnerClass = FuncRef.GetMemberParentClass(CurrentClass);
const bool bFunctionOwnerIsNotParentOfClass = !FuncOwnerClass || !ExtendedIsParent(FuncOwnerClass, CurrentClass);
const bool bNeedsToBeFixed = !TargetFunction || bFunctionOwnerIsNotParentOfClass;
if (bNeedsToBeFixed)
{
const UClass* SuperClass = CurrentClass->GetSuperClass();
const UFunction* ActualTargetFunction = SuperClass ? SuperClass->FindFunctionByName(EventFuncName) : nullptr;
if (ActualTargetFunction)
{
ensure(TargetFunction != ActualTargetFunction);
if (!ensure(!TargetFunction || TargetFunction->IsSignatureCompatibleWith(ActualTargetFunction)))
{
UE_LOG(LogBlueprint, Error
, TEXT("FixOverriddenEventSignature function \"%s\" is not compatible with \"%s\" node \"%s\"")
, *GetPathNameSafe(ActualTargetFunction), *GetPathNameSafe(TargetFunction), *GetPathNameSafe(EventNode));
}
ensure(GetDefault<UEdGraphSchema_K2>()->FunctionCanBePlacedAsEvent(ActualTargetFunction));
FuncRef.SetFromField<UFunction>(ActualTargetFunction, false);
// Emit something to the log to indicate that we've made a change
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), EventNode->GetNodeTitle(ENodeTitleType::ListView));
Args.Add(TEXT("EventNodeName"), FText::FromString(EventNode->GetName()));
Blueprint->Message_Note(FText::Format(LOCTEXT("EventSignatureFixed_Note", "{NodeTitle} ({EventNodeName}) had an invalid function signature - it has now been fixed."), Args).ToString());
}
else
{
TSet<FName> DummyExtraNameList;
UEdGraphNode* CustomEventNode = CurrentGraph->GetSchema()->CreateSubstituteNode(EventNode, CurrentGraph, nullptr, DummyExtraNameList);
if (ensure(CustomEventNode))
{
// Destroy the old event node (this will also break all pin links and remove it from the graph)
EventNode->DestroyNode();
// Add the new custom event node to the graph
CurrentGraph->Nodes.Add(CustomEventNode);
// Emit something to the log to indicate that we've made a change
FFormatNamedArguments Args;
Args.Add(TEXT("NodeTitle"), EventNode->GetNodeTitle(ENodeTitleType::ListView));
Args.Add(TEXT("EventNodeName"), FText::FromString(EventNode->GetName()));
Blueprint->Message_Note(FText::Format(LOCTEXT("EventNodeReplaced_Note", "{NodeTitle} ({EventNodeName}) was not valid for this Blueprint - it has been converted to a custom event."), Args).ToString());
}
}
}
}
}
// Makes sure that all events we handle exist, and replace with custom events if not
void FBlueprintEditorUtils::ConformImplementedEvents(UBlueprint* Blueprint)
{
check(nullptr != Blueprint);
// Collect all implemented interface classes
TArray<UClass*> ImplementedInterfaceClasses;
FindImplementedInterfaces(Blueprint, true, ImplementedInterfaceClasses);
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
for(int GraphIndex = 0; GraphIndex < AllGraphs.Num(); ++GraphIndex)
{
UEdGraph* CurrentGraph = AllGraphs[GraphIndex];
check(nullptr != CurrentGraph);
// Make sure the graph is loaded
if(!CurrentGraph->HasAnyFlags(RF_NeedLoad|RF_NeedPostLoad))
{
TArray<UK2Node_Event*> EventNodes;
CurrentGraph->GetNodesOfClass<UK2Node_Event>(EventNodes);
// For each event node in the graph
for(int EventNodeIndex = 0; EventNodeIndex < EventNodes.Num(); ++EventNodeIndex)
{
UK2Node_Event* EventNode = EventNodes[EventNodeIndex];
check(nullptr != EventNode);
// If the event is loaded and is not a custom event
if(!EventNode->HasAnyFlags(RF_NeedLoad|RF_NeedPostLoad) && EventNode->bOverrideFunction)
{
UClass* EventClass = EventNode->EventReference.GetMemberParentClass(EventNode->GetBlueprintClassFromNode());
bool bEventNodeUsedByInterface = false;
int32 Idx = 0;
while (Idx != ImplementedInterfaceClasses.Num() && bEventNodeUsedByInterface == false)
{
const UClass* CurrentInterface = ImplementedInterfaceClasses[Idx];
while (CurrentInterface)
{
if (EventClass == CurrentInterface )
{
bEventNodeUsedByInterface = true;
break;
}
CurrentInterface = CurrentInterface->GetSuperClass();
}
++Idx;
}
if (Blueprint->GeneratedClass && !bEventNodeUsedByInterface)
{
FixOverriddenEventSignature(EventNode, Blueprint, CurrentGraph);
}
}
}
}
}
}
/** Helper function for ConformImplementedInterfaces */
static void ConformInterfaceByGUID(const UBlueprint* Blueprint, const FBPInterfaceDescription& CurrentInterfaceDesc)
{
// Conform anim layers before interface graphs as GUIDs may need to be set up in older assets.
if (const UAnimBlueprint * AnimBlueprint = Cast<UAnimBlueprint>(Blueprint))
{
UAnimationGraphSchema::ConformAnimLayersByGuid(AnimBlueprint, CurrentInterfaceDesc);
}
// Attempt to conform by GUID if we have a blueprint interface
// This just make sure that GUID-linked functions preserve their names
const UBlueprint* InterfaceBlueprint = CastChecked<UBlueprint>(CurrentInterfaceDesc.Interface->ClassGeneratedBy);
TArray<UEdGraph*> InterfaceGraphs;
InterfaceBlueprint->GetAllGraphs(InterfaceGraphs);
TArray<UEdGraph*> BlueprintGraphs;
Blueprint->GetAllGraphs(BlueprintGraphs);
for (UEdGraph* BlueprintGraph : BlueprintGraphs)
{
if(BlueprintGraph != nullptr && BlueprintGraph->InterfaceGuid.IsValid())
{
// valid interface Guid found, so fixup name if it is different
for (const UEdGraph* InterfaceGraph : InterfaceGraphs)
{
if(InterfaceGraph != nullptr && InterfaceGraph->GraphGuid == BlueprintGraph->InterfaceGuid && InterfaceGraph->GetFName() != BlueprintGraph->GetFName())
{
FBlueprintEditorUtils::RenameGraph(BlueprintGraph, InterfaceGraph->GetFName().ToString());
FBlueprintEditorUtils::RefreshGraphNodes(BlueprintGraph);
break;
}
}
}
}
}
/** Helper function for ConformImplementedInterfaces */
static void ConformInterfaceByName(UBlueprint* Blueprint, FBPInterfaceDescription& CurrentInterfaceDesc, int32 InterfaceIndex, TArray<UK2Node_Event*>& ImplementedEvents, const TArray<FName>& VariableNamesUsedInBlueprint)
{
// Iterate over all the functions in the interface, and create graphs that are in the interface, but missing in the blueprint
if (CurrentInterfaceDesc.Interface)
{
// a interface could have since been added by the parent (or this blueprint could have been re-parented)
if (IsInterfaceImplementedByParent(CurrentInterfaceDesc, Blueprint))
{
// have to remove the interface before we promote it (in case this method is reentrant)
FBPInterfaceDescription LocalInterfaceCopy = CurrentInterfaceDesc;
Blueprint->ImplementedInterfaces.RemoveAt(InterfaceIndex, 1);
// in this case, the interface needs to belong to the parent and not this
// blueprint (we would have been prevented from getting in this state if we
// had started with a parent that implemented this interface initially)
PromoteInterfaceImplementationToOverride(LocalInterfaceCopy, Blueprint);
return;
}
// check to make sure that there aren't any interface methods that we originally
// implemented as events, but have since switched to functions
TSet<FName> ExtraNameList;
for (UK2Node_Event* EventNode : ImplementedEvents)
{
// if this event belongs to something other than this interface
if (EventNode->EventReference.GetMemberParentClass(EventNode->GetBlueprintClassFromNode()) != CurrentInterfaceDesc.Interface)
{
continue;
}
UFunction* InterfaceFunction = EventNode->EventReference.ResolveMember<UFunction>(CurrentInterfaceDesc.Interface);
// if the function is still ok as an event, no need to try and fix it up
if (UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(InterfaceFunction))
{
continue;
}
UEdGraph* EventGraph = EventNode->GetGraph();
// we've already implemented this interface function as an event (which we need to replace)
UK2Node_CustomEvent* CustomEventNode = Cast<UK2Node_CustomEvent>(EventGraph->GetSchema()->CreateSubstituteNode(EventNode, EventGraph, nullptr, ExtraNameList));
if (CustomEventNode == nullptr)
{
continue;
}
// grab the function's name before we delete the node
FName const FunctionName = EventNode->EventReference.GetMemberName();
// destroy the old event node (this will also break all pin links and remove it from the graph)
EventNode->DestroyNode();
if (InterfaceFunction)
{
// have to rename so it doesn't conflict with the graph we're about to add
CustomEventNode->RenameCustomEventCloseToName();
}
EventGraph->Nodes.Add(CustomEventNode);
// warn the user that their old functionality won't work (it's now connected
// to a custom node that isn't triggered anywhere)
FText WarningMessageText;
if (InterfaceFunction)
{
WarningMessageText = FText::Format(
LOCTEXT("InterfaceEventNodeReplaced_WarnFmt", "'{0}' was promoted from an event to a function - it has been replaced by a custom event, which won't trigger unless you call it manually."),
FText::FromName(FunctionName)
);
}
else
{
WarningMessageText = FText::Format(
LOCTEXT("InterfaceEventRemovedNodeReplaced_WarnFmt", "'{0}' was removed from its interface - it has been replaced by a custom event, which won't trigger unless you call it manually."),
FText::FromName(FunctionName)
);
}
Blueprint->Message_Warn(WarningMessageText.ToString());
}
// Cache off the graph names for this interface, for easier searching
TMap<FName, UEdGraph*> InterfaceFunctionGraphs;
for (int32 GraphIndex = 0; GraphIndex < CurrentInterfaceDesc.Graphs.Num(); GraphIndex++)
{
UEdGraph* CurrentGraph = CurrentInterfaceDesc.Graphs[GraphIndex];
if( CurrentGraph )
{
InterfaceFunctionGraphs.Add(CurrentGraph->GetFName()) = CurrentGraph;
}
}
// If this is a Blueprint interface, redirect to the skeleton class for function iteration
const UClass* InterfaceClass = CurrentInterfaceDesc.Interface;
if (InterfaceClass && InterfaceClass->ClassGeneratedBy)
{
InterfaceClass = CastChecked<UBlueprint>(InterfaceClass->ClassGeneratedBy)->SkeletonGeneratedClass;
}
// Iterate over all the functions in the interface, and create graphs that are in the interface, but missing in the blueprint
for (TFieldIterator<UFunction> FunctionIter(InterfaceClass, EFieldIteratorFlags::IncludeSuper); FunctionIter; ++FunctionIter)
{
UFunction* Function = *FunctionIter;
const FName FunctionName = Function->GetFName();
if(!VariableNamesUsedInBlueprint.Contains(FunctionName))
{
if( UEdGraphSchema_K2::CanKismetOverrideFunction(Function) && !UEdGraphSchema_K2::FunctionCanBePlacedAsEvent(Function) )
{
if (UEdGraph** FunctionGraphPtr = InterfaceFunctionGraphs.Find(FunctionName))
{
const bool bIsConstInterfaceFunction = (Function->FunctionFlags & FUNC_Const) != 0;
// Sync the 'const' attribute of the implementation with the interface function, in case it has been changed
TArray<UK2Node_FunctionEntry*> FunctionEntryNodes;
(*FunctionGraphPtr)->GetNodesOfClass<UK2Node_FunctionEntry>(FunctionEntryNodes);
for (UK2Node_FunctionEntry* FunctionEntryNode : FunctionEntryNodes)
{
const bool bIsImplementedAsConstFunction = (FunctionEntryNode->GetExtraFlags() & FUNC_Const) != 0;
if (bIsImplementedAsConstFunction != bIsConstInterfaceFunction)
{
FunctionEntryNode->Modify();
if (bIsConstInterfaceFunction)
{
FunctionEntryNode->AddExtraFlags(FUNC_Const);
}
else
{
FunctionEntryNode->ClearExtraFlags(FUNC_Const);
}
}
}
}
else
{
// interface methods initially create EventGraph stubs, so we need
// to make sure we remove that entry so the new graph doesn't conflict (don't
// worry, these are regenerated towards the end of a compile)
for (UEdGraph* GraphStub : Blueprint->EventGraphs)
{
if (GraphStub->GetFName() == FunctionName)
{
FBlueprintEditorUtils::RemoveGraph(Blueprint, GraphStub, EGraphRemoveFlags::MarkTransient);
}
}
// Check to see if we already have implemented
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, FunctionName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
NewGraph->bAllowDeletion = false;
NewGraph->InterfaceGuid = FBlueprintEditorUtils::FindInterfaceFunctionGuid(Function, CurrentInterfaceDesc.Interface);
CurrentInterfaceDesc.Graphs.Add(NewGraph);
FBlueprintEditorUtils::AddInterfaceGraph(Blueprint, NewGraph, CurrentInterfaceDesc.Interface);
}
}
else if(Function->HasMetaData(FBlueprintMetadata::MD_AnimBlueprintFunction))
{
if (UEdGraph** FunctionGraphPtr = InterfaceFunctionGraphs.Find(FunctionName))
{
UAnimationGraphSchema::ConformAnimGraphToInterface(Blueprint, *(*FunctionGraphPtr), Function);
}
// We perform the check here to avoid creating a graph if it isnt implemented in the full interface (note not the skeleton interface that we are iterating over)
// this is to avoid creating it then removing the graph below if it isnt present in the full class, which will cause a name conflict second time around
else if(FindUField<UFunction>(CurrentInterfaceDesc.Interface, FunctionName))
{
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, FunctionName, UAnimationGraph::StaticClass(), UAnimationGraphSchema::StaticClass());
NewGraph->bAllowDeletion = false;
NewGraph->InterfaceGuid = FBlueprintEditorUtils::FindInterfaceFunctionGuid(Function, CurrentInterfaceDesc.Interface);
CurrentInterfaceDesc.Graphs.Add(NewGraph);
FBlueprintEditorUtils::AddInterfaceGraph(Blueprint, NewGraph, CurrentInterfaceDesc.Interface);
}
}
}
else
{
Blueprint->Status = BS_Error;
const FString NewError = FText::Format(
LOCTEXT("InterfaceNameCollision_ErrorFmt", "Interface name collision in blueprint: {0}, interface: {1}, name: {2}"),
FText::FromString(Blueprint->GetFullName()),
FText::FromString(CurrentInterfaceDesc.Interface->GetFullName()),
FText::FromName(FunctionName)
).ToString();
Blueprint->Message_Error(NewError);
}
}
// Iterate over all the graphs in the blueprint interface, and remove ones that no longer have functions
for (int32 GraphIndex = 0; GraphIndex < CurrentInterfaceDesc.Graphs.Num(); GraphIndex++)
{
// If we can't find the function associated with the graph, delete it
UEdGraph* CurrentGraph = CurrentInterfaceDesc.Graphs[GraphIndex];
if (!CurrentGraph || !FindUField<UFunction>(CurrentInterfaceDesc.Interface, CurrentGraph->GetFName()))
{
if(CurrentGraph)
{
CurrentGraph->GetSchema()->HandleGraphBeingDeleted(*CurrentGraph);
// rename to free up the graph's name.. which may be needed by an inherited function
// alternatively we could move this into the functions list?
CurrentGraph->Rename(
nullptr,
CurrentGraph->GetOuter(),
REN_DoNotDirty | REN_DontCreateRedirectors);
// removing from root, standalone, and public is defensive to make sure it is not saved:
CurrentGraph->ClearFlags(RF_Standalone | RF_Public);
CurrentGraph->RemoveFromRoot();
// MarkAsGarbage could be used here, which would nicely trigger tab manager cleanup, but atm
// use of MarkAsGarbage is causing SGraphPanel to have reference's nulled out (treated as weak)
// by the GC. For now, I'm just going to flag this as no longer editable. This isn't a bad
// out come as if the user has anything in the graph they might be able to copy it out
CurrentGraph->bEditable = false;
}
CurrentInterfaceDesc.Graphs.RemoveAt(GraphIndex, 1);
GraphIndex--;
}
}
}
}
// Makes sure that all graphs for all interfaces we implement exist, and add if not
void FBlueprintEditorUtils::ConformImplementedInterfaces(UBlueprint* Blueprint)
{
check(nullptr != Blueprint);
FString ErrorStr;
// Collect all variables names in current blueprint
TArray<FName> VariableNamesUsedInBlueprint;
for (TFieldIterator<FProperty> VariablesIter(Blueprint->GeneratedClass); VariablesIter; ++VariablesIter)
{
VariableNamesUsedInBlueprint.Add(VariablesIter->GetFName());
}
for (const FBPVariableDescription& NewVariable : Blueprint->NewVariables)
{
VariableNamesUsedInBlueprint.AddUnique(NewVariable.VarName);
}
TArray<UEdGraph*> AllGraphs;
Blueprint->GetAllGraphs(AllGraphs);
// collect all existing event nodes, so we can find interface events that
// need to be converted to function graphs
TArray<UK2Node_Event*> PotentialInterfaceEvents;
for (UEdGraph const* Graph : AllGraphs)
{
UClass* InterfaceEventClass = UK2Node_Event::StaticClass();
for (UEdGraphNode* GraphNode : Graph->Nodes)
{
// interface event nodes are only ever going to be implemented as
// explicit UK2Node_Events... using == instead of IsChildOf<>
// guarantees that we won't be catching any special node types that
// users might have made (that maybe reference interface functions too)
if (GraphNode && (GraphNode->GetClass() == InterfaceEventClass))
{
PotentialInterfaceEvents.Add(CastChecked<UK2Node_Event>(GraphNode));
}
}
}
for (int32 InterfaceIndex = 0; InterfaceIndex < Blueprint->ImplementedInterfaces.Num(); )
{
FBPInterfaceDescription& CurrentInterface = Blueprint->ImplementedInterfaces[InterfaceIndex];
if (!CurrentInterface.Interface)
{
Blueprint->Status = BS_Error;
Blueprint->ImplementedInterfaces.RemoveAt(InterfaceIndex, 1);
continue;
}
// conform functions linked by Guids first
if(CurrentInterface.Interface->ClassGeneratedBy != nullptr && CurrentInterface.Interface->ClassGeneratedBy->IsA(UBlueprint::StaticClass()))
{
ConformInterfaceByGUID(Blueprint, CurrentInterface);
}
// now try to conform by name/signature
ConformInterfaceByName(Blueprint, CurrentInterface, InterfaceIndex, PotentialInterfaceEvents, VariableNamesUsedInBlueprint);
// not going to remove this interface, so let's continue forward
++InterfaceIndex;
}
}
void FBlueprintEditorUtils::ConformDelegateSignatureGraphs(UBlueprint* Blueprint)
{
Blueprint->DelegateSignatureGraphs.SetNum( Algo::RemoveIf(Blueprint->DelegateSignatureGraphs,
[Blueprint](UEdGraph* Graph)
{
const int32 VarIndex = FBlueprintEditorUtils::FindNewVariableIndex(Blueprint, Graph->GetFName());
return VarIndex == INDEX_NONE;
}
));
}
void FBlueprintEditorUtils::ConformAllowDeletionFlag(UBlueprint* Blueprint)
{
for (UEdGraph* Graph : Blueprint->FunctionGraphs)
{
if (Graph->GetFName() != UEdGraphSchema_K2::FN_UserConstructionScript && Graph->GetFName() != UEdGraphSchema_K2::GN_AnimGraph)
{
Graph->bAllowDeletion = true;
}
}
}
/** Handle old Anim Blueprints (state machines in the wrong position, transition graphs with the wrong schema, etc...) */
void FBlueprintEditorUtils::UpdateOutOfDateAnimBlueprints(UBlueprint* InBlueprint)
{
if (UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(InBlueprint))
{
// Ensure all transition graphs have the correct schema
TArray<UAnimStateTransitionNode*> TransitionNodes;
GetAllNodesOfClass<UAnimStateTransitionNode>(AnimBlueprint, /*out*/ TransitionNodes);
for (UAnimStateTransitionNode* Node : TransitionNodes)
{
UEdGraph* TestGraph = Node->BoundGraph;
if (TestGraph->Schema == UAnimationGraphSchema::StaticClass())
{
TestGraph->Schema = UAnimationTransitionSchema::StaticClass();
}
}
// Handle a reparented anim blueprint that either needs or no longer needs an anim graph
if(AnimBlueprint->BlueprintType != BPTYPE_Interface)
{
if (UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint) == nullptr)
{
// Add an anim graph if not present
if (FindObject<UEdGraph>(AnimBlueprint, *(UEdGraphSchema_K2::GN_AnimGraph.ToString())) == nullptr)
{
UEdGraph* NewGraph = FBlueprintEditorUtils::CreateNewGraph(AnimBlueprint, UEdGraphSchema_K2::GN_AnimGraph, UAnimationGraph::StaticClass(), UAnimationGraphSchema::StaticClass());
FBlueprintEditorUtils::AddDomainSpecificGraph(AnimBlueprint, NewGraph);
AnimBlueprint->LastEditedDocuments.Add(NewGraph);
NewGraph->bAllowDeletion = false;
}
}
else
{
// Remove an anim graph if present
for (int32 i = 0; i < AnimBlueprint->FunctionGraphs.Num(); ++i)
{
UEdGraph* FuncGraph = AnimBlueprint->FunctionGraphs[i];
if ((FuncGraph != nullptr) && (FuncGraph->GetFName() == UEdGraphSchema_K2::GN_AnimGraph))
{
UE_LOG(LogBlueprint, Log, TEXT("!!! Removing AnimGraph from %s, because it has a parent anim blueprint that defines the AnimGraph"), *AnimBlueprint->GetPathName());
AnimBlueprint->FunctionGraphs.RemoveAt(i);
break;
}
}
}
}
}
}
void FBlueprintEditorUtils::UpdateOutOfDateCompositeNodes(UBlueprint* Blueprint)
{
for (UEdGraph* UbergraphPage : Blueprint->UbergraphPages)
{
UpdateOutOfDateCompositeWithOuter(Blueprint, UbergraphPage);
}
for (UEdGraph* FunctionGraph : Blueprint->FunctionGraphs)
{
UpdateOutOfDateCompositeWithOuter(Blueprint, FunctionGraph);
}
}
void FBlueprintEditorUtils::UpdateOutOfDateCompositeWithOuter(UBlueprint* Blueprint, UEdGraph* OuterGraph )
{
check(OuterGraph != nullptr);
check(FindBlueprintForGraphChecked(OuterGraph) == Blueprint);
for (UEdGraphNode* Node : OuterGraph->Nodes)
{
//Is this node of a type that has a BoundGraph to update
for(UEdGraph* BoundGraph : Node->GetSubGraphs())
{
if (BoundGraph)
{
// Check for out of date BoundGraph where outer is not the composite node
if (BoundGraph->GetOuter() != Node)
{
// change the outer of the BoundGraph to be the composite node instead of the OuterGraph
if (false == BoundGraph->Rename(*BoundGraph->GetName(), Node, REN_DontCreateRedirectors))
{
UE_LOG(LogBlueprintDebug, Log, TEXT("CompositeNode: On Blueprint '%s' could not fix Outer() for BoundGraph of composite node '%s'"), *Blueprint->GetPathName(), *Node->GetName());
}
}
}
}
}
for (UEdGraph* SubGraph : OuterGraph->SubGraphs)
{
UpdateOutOfDateCompositeWithOuter(Blueprint, SubGraph);
}
}
/** Ensure all component templates are in use */
void FBlueprintEditorUtils::UpdateComponentTemplates(UBlueprint* Blueprint)
{
TArray<UActorComponent*> ReferencedTemplates;
TArray<UK2Node_AddComponent*> AllComponents;
FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, AllComponents);
for (UK2Node_AddComponent* ComponentNode : AllComponents)
{
check(ComponentNode);
UActorComponent* ActorComp = ComponentNode->GetTemplateFromNode();
if (ActorComp)
{
ensure(Blueprint->ComponentTemplates.Contains(ActorComp));
// fix up AddComponent nodes that don't have their own unique template objects
if (ReferencedTemplates.Contains(ActorComp))
{
UE_LOG(LogBlueprint, Warning,
TEXT("Blueprint '%s' has an AddComponent node '%s' with a non-unique component template name (%s). Moving it to a new template object with a unique name. Re-save the Blueprint to remove this warning on the next load."),
*Blueprint->GetPathName(), *ComponentNode->GetPathName(), *ActorComp->GetName());
ComponentNode->MakeNewComponentTemplate();
ActorComp = ComponentNode->GetTemplateFromNode();
}
// fix up existing content to be sure these are flagged as archetypes and are transactional
ActorComp->SetFlags(RF_ArchetypeObject|RF_Transactional);
ReferencedTemplates.Add(ActorComp);
}
}
Blueprint->ComponentTemplates.Empty();
Blueprint->ComponentTemplates.Append(ReferencedTemplates);
}
/** Ensures that the CDO root component reference is valid for Actor-based Blueprints */
void FBlueprintEditorUtils::UpdateRootComponentReference(UBlueprint* Blueprint)
{
// The CDO's root component reference should match that of its parent class
if(Blueprint && Blueprint->ParentClass && Blueprint->GeneratedClass)
{
AActor* ParentActorCDO = Cast<AActor>(Blueprint->ParentClass->GetDefaultObject(false));
AActor* BlueprintActorCDO = Cast<AActor>(Blueprint->GeneratedClass->GetDefaultObject(false));
if(ParentActorCDO && BlueprintActorCDO)
{
// If both CDOs are valid, check for a valid scene root component
USceneComponent* ParentSceneRootComponent = ParentActorCDO->GetRootComponent();
USceneComponent* BlueprintSceneRootComponent = BlueprintActorCDO->GetRootComponent();
if((ParentSceneRootComponent == nullptr && BlueprintSceneRootComponent != nullptr)
|| (ParentSceneRootComponent != nullptr && BlueprintSceneRootComponent == nullptr)
|| (ParentSceneRootComponent != nullptr && BlueprintSceneRootComponent != nullptr && ParentSceneRootComponent->GetFName() != BlueprintSceneRootComponent->GetFName()))
{
// If the parent CDO has a valid scene root component
if(ParentSceneRootComponent != nullptr)
{
// Search for a scene component with the same name in the Blueprint CDO's Components list
TInlineComponentArray<USceneComponent*> SceneComponents;
BlueprintActorCDO->GetComponents(SceneComponents);
for(int i = 0; i < SceneComponents.Num(); ++i)
{
USceneComponent* SceneComp = SceneComponents[i];
if(SceneComp && SceneComp->GetFName() == ParentSceneRootComponent->GetFName())
{
// We found a match, so make this the new scene root component
BlueprintActorCDO->SetRootComponent(SceneComp);
break;
}
}
}
else if(BlueprintSceneRootComponent != nullptr)
{
// The parent CDO does not have a valid scene root, so NULL out the Blueprint CDO reference to match
BlueprintActorCDO->SetRootComponent(nullptr);
}
}
}
}
}
bool FBlueprintEditorUtils::IsSCSComponentProperty(FObjectProperty* MemberProperty)
{
if (!MemberProperty->PropertyClass->IsChildOf<UActorComponent>())
{
return false;
}
UClass* OwnerClass = MemberProperty->GetOwnerClass();
UBlueprintGeneratedClass* BpClassOwner = Cast<UBlueprintGeneratedClass>(OwnerClass);
if (BpClassOwner == nullptr)
{
// if this isn't directly a blueprint property, then we check if it is a
// associated with a natively added component (which would still be
// accessible through the SCS tree)
if ((OwnerClass == nullptr) || !OwnerClass->IsChildOf<AActor>())
{
return false;
}
else if (const AActor* ActorCDO = GetDefault<AActor>(OwnerClass))
{
const void* PropertyAddress = MemberProperty->ContainerPtrToValuePtr<void>(ActorCDO);
UObject* PropertyValue = MemberProperty->GetObjectPropertyValue(PropertyAddress);
for (UActorComponent* Component : ActorCDO->GetComponents())
{
if (Component && Component->GetClass()->IsChildOf(MemberProperty->PropertyClass))
{
if (PropertyValue == Component)
{
return true;
}
}
}
}
return false;
}
FMemberReference MemberRef;
MemberRef.SetFromField<FProperty>(MemberProperty, /*bIsConsideredSelfContext =*/false);
bool const bIsGuidValid = MemberRef.GetMemberGuid().IsValid();
if (BpClassOwner->SimpleConstructionScript != nullptr)
{
TArray<USCS_Node*> SCSNodes = BpClassOwner->SimpleConstructionScript->GetAllNodes();
for (USCS_Node* ScsNode : SCSNodes)
{
if (bIsGuidValid && ScsNode->VariableGuid.IsValid())
{
if (ScsNode->VariableGuid == MemberRef.GetMemberGuid())
{
return true;
}
}
else if (ScsNode->GetVariableName() == MemberRef.GetMemberName())
{
return true;
}
}
}
return false;
}
UActorComponent* FBlueprintEditorUtils::FindUCSComponentTemplate(const FComponentKey& ComponentKey, const FName& TemplateName)
{
UActorComponent* FoundTemplate = nullptr;
if (ComponentKey.IsValid() && ComponentKey.IsUCSKey())
{
if (UBlueprint* Blueprint = Cast<UBlueprint>(ComponentKey.GetComponentOwner()->ClassGeneratedBy))
{
if (UEdGraph* UCSGraph = FBlueprintEditorUtils::FindUserConstructionScript(Blueprint))
{
TArray<UK2Node_AddComponent*> ComponentNodes;
UCSGraph->GetNodesOfClass<UK2Node_AddComponent>(ComponentNodes);
for (UK2Node_AddComponent* UCSNode : ComponentNodes)
{
if (UCSNode->NodeGuid == ComponentKey.GetAssociatedGuid())
{
FoundTemplate = UCSNode->GetTemplateFromNode();
break;
}
}
}
}
else if(UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(ComponentKey.GetComponentOwner()))
{
for (UActorComponent* ComponentTemplate : BPGC->ComponentTemplates)
{
if (ComponentTemplate->GetFName().IsEqual(TemplateName))
{
FoundTemplate = ComponentTemplate;
break;
}
}
}
}
return FoundTemplate;
}
/** Temporary fix for cut-n-paste error that failed to carry transactional flags */
void FBlueprintEditorUtils::UpdateTransactionalFlags(UBlueprint* Blueprint)
{
TArray<UK2Node*> AllNodes;
FBlueprintEditorUtils::GetAllNodesOfClass(Blueprint, AllNodes);
for (UK2Node* K2Node : AllNodes)
{
check(K2Node);
if (!K2Node->HasAnyFlags(RF_Transactional))
{
K2Node->SetFlags(RF_Transactional);
Blueprint->Status = BS_Dirty;
}
}
}
void FBlueprintEditorUtils::UpdateStalePinWatches( UBlueprint* Blueprint )
{
TSet<FBlueprintWatchedPin> AllPins;
uint16 WatchCount = 0;
// Find all unique pins being watched
FKismetDebugUtilities::ForeachPinWatch(
Blueprint,
[&AllPins, &WatchCount](const FBlueprintWatchedPin& WatchedPin)
{
++WatchCount;
UEdGraphPin* Pin = WatchedPin.Get();
if (Pin == nullptr)
{
return; // ~continue
}
UEdGraphNode* OwningNode = Pin->GetOwningNode();
// during node reconstruction, dead pins get moved to the transient
// package (so just in case this blueprint got saved with dead pin watches)
if (OwningNode == nullptr)
{
return; // ~continue
}
if (!OwningNode->Pins.Contains(Pin))
{
return; // ~continue
}
AllPins.Add(WatchedPin);
}
);
// Refresh watched pins with unique pins (throw away null or duplicate watches)
if (WatchCount != AllPins.Num())
{
FKismetDebugUtilities::ClearPinWatches(Blueprint);
for (FBlueprintWatchedPin& WatchedPin : AllPins)
{
FKismetDebugUtilities::AddPinWatch(Blueprint, MoveTemp(WatchedPin));
}
}
}
void FBlueprintEditorUtils::ClearMacroCosmeticInfoCache(UBlueprint* Blueprint)
{
Blueprint->PRIVATE_CachedMacroInfo.Reset();
}
FBlueprintMacroCosmeticInfo FBlueprintEditorUtils::GetCosmeticInfoForMacro(UEdGraph* MacroGraph)
{
if (UBlueprint* MacroOwnerBP = FBlueprintEditorUtils::FindBlueprintForGraph(MacroGraph))
{
checkSlow(MacroGraph->GetSchema()->GetGraphType(MacroGraph) == GT_Macro);
// See if it's in the cache
if (FBlueprintMacroCosmeticInfo* pCosmeticInfo = MacroOwnerBP->PRIVATE_CachedMacroInfo.Find(MacroGraph))
{
return *pCosmeticInfo;
}
else
{
FBlueprintMacroCosmeticInfo& CosmeticInfo = MacroOwnerBP->PRIVATE_CachedMacroInfo.Add(MacroGraph);
CosmeticInfo.bContainsLatentNodes = FBlueprintEditorUtils::CheckIfGraphHasLatentFunctions(MacroGraph);
return CosmeticInfo;
}
}
return FBlueprintMacroCosmeticInfo();
}
void FBlueprintEditorUtils::ReplaceInvalidBlueprintNameCharacters(FString& InBaseName)
{
for (TCHAR& TestChar : InBaseName)
{
for (TCHAR BadChar : UE_BLUEPRINT_INVALID_NAME_CHARACTERS)
{
if (TestChar == BadChar)
{
TestChar = TEXT('_');
break;
}
}
}
}
FName FBlueprintEditorUtils::FindUniqueKismetName(const UBlueprint* InBlueprint, const FString& InBaseName, UStruct* InScope/* = nullptr*/)
{
int32 Count = 0;
// If an empty string is given then we need to give a valid backup
static const FString BackupKismetName = TEXT("K2Name");
FString BaseName = InBaseName.IsEmpty() ? BackupKismetName : InBaseName;
FString KismetName = InBaseName;
TSharedPtr<FKismetNameValidator> NameValidator = MakeShareable(new FKismetNameValidator(InBlueprint, NAME_None, InScope));
EValidatorResult Result = NameValidator->IsValid(KismetName);
// Clean up BaseName to not contain any invalid characters, which will mean we can never find a legal name no matter how many numbers we add
if (Result == EValidatorResult::ContainsInvalidCharacters)
{
ReplaceInvalidBlueprintNameCharacters(BaseName);
KismetName = BaseName;
Result = NameValidator->IsValid(KismetName);
}
while(Result != EValidatorResult::Ok)
{
// Calculate the number of digits in the number, adding 2 (1 extra to correctly count digits, another to account for the '_' that will be added to the name
int32 CountLength = Count > 0? (int32)log((double)Count) + 2 : 2;
// If the length of the final string will be too long, cut off the end so we can fit the number
if(CountLength + BaseName.Len() > NameValidator->GetMaximumNameLength())
{
BaseName.LeftInline(NameValidator->GetMaximumNameLength() - CountLength, EAllowShrinking::No);
}
KismetName = FString::Printf(TEXT("%s_%d"), *BaseName, Count);
Count++;
Result = NameValidator->IsValid(KismetName);
}
return FName(*KismetName);
}
FName FBlueprintEditorUtils::FindUniqueCustomEventName(const UBlueprint* Blueprint)
{
return FindUniqueKismetName(Blueprint, LOCTEXT("DefaultCustomEventName", "CustomEvent").ToString());
}
//////////////////////////////////////////////////////////////////////////
// Timeline
FName FBlueprintEditorUtils::FindUniqueTimelineName(const UBlueprint* Blueprint)
{
return FindUniqueKismetName(Blueprint, LOCTEXT("DefaultTimelineName", "Timeline").ToString());
}
UTimelineTemplate* FBlueprintEditorUtils::AddNewTimeline(UBlueprint* Blueprint, const FName& TimelineVarName)
{
// Early out if we don't support timelines in this class
if( !FBlueprintEditorUtils::DoesSupportTimelines(Blueprint) )
{
return nullptr;
}
// First look to see if we already have a timeline with that name
UTimelineTemplate* Timeline = Blueprint->FindTimelineTemplateByVariableName(TimelineVarName);
if (Timeline != nullptr)
{
UE_LOG(LogBlueprint, Log, TEXT("AddNewTimeline: Blueprint '%s' already contains a timeline called '%s'"), *Blueprint->GetPathName(), *TimelineVarName.ToString());
return nullptr;
}
else
{
Blueprint->Modify();
check(nullptr != Blueprint->GeneratedClass);
// Construct new graph with the supplied name
const FName TimelineTemplateName = *UTimelineTemplate::TimelineVariableNameToTemplateName(TimelineVarName);
Timeline = NewObject<UTimelineTemplate>(Blueprint->GeneratedClass, TimelineTemplateName, RF_Transactional);
Blueprint->Timelines.Add(Timeline);
// Potentially adjust variable names for any child blueprints
FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, TimelineVarName);
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
return Timeline;
}
}
void FBlueprintEditorUtils::RemoveTimeline(UBlueprint* Blueprint, UTimelineTemplate* Timeline, bool bDontRecompile)
{
// Ensure objects are marked modified
Timeline->Modify();
Blueprint->Modify();
Blueprint->Timelines.Remove(Timeline);
Timeline->MarkAsGarbage();
if( !bDontRecompile )
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
}
UK2Node_Timeline* FBlueprintEditorUtils::FindNodeForTimeline(UBlueprint* Blueprint, UTimelineTemplate* Timeline)
{
check(Timeline);
const FName TimelineVarName = Timeline->GetVariableName();
TArray<UK2Node_Timeline*> TimelineNodes;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Timeline>(Blueprint, TimelineNodes);
for(int32 i=0; i<TimelineNodes.Num(); i++)
{
UK2Node_Timeline* TestNode = TimelineNodes[i];
if(TestNode->TimelineName == TimelineVarName)
{
return TestNode;
}
}
return nullptr; // no node found
}
bool FBlueprintEditorUtils::RenameTimeline(UBlueprint* Blueprint, const FName OldNameRef, const FName NewName)
{
check(Blueprint);
bool bRenamed = false;
// make a copy, in case we alter the value of what is referenced by
// OldNameRef through the course of this function
FName OldName = OldNameRef;
TSharedPtr<INameValidatorInterface> NameValidator = MakeShareable(new FKismetNameValidator(Blueprint));
const FString NewTemplateName = UTimelineTemplate::TimelineVariableNameToTemplateName(NewName);
// NewName should be already validated. But one must make sure that NewTemplateName is also unique.
const bool bUniqueNameForTemplate = (EValidatorResult::Ok == NameValidator->IsValid(NewTemplateName));
UTimelineTemplate* Template = Blueprint->FindTimelineTemplateByVariableName(OldName);
UTimelineTemplate* NewTemplate = Blueprint->FindTimelineTemplateByVariableName(NewName);
if ((!NewTemplate && bUniqueNameForTemplate) || NewTemplate == Template)
{
if (Template)
{
Blueprint->Modify();
Template->Modify();
if (UK2Node_Timeline* TimelineNode = FindNodeForTimeline(Blueprint, Template))
{
TimelineNode->Modify();
TimelineNode->TimelineName = NewName;
}
TArray<UK2Node_Variable*> TimelineVarNodes;
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Variable>(Blueprint, TimelineVarNodes);
for(int32 It = 0; It < TimelineVarNodes.Num(); It++)
{
UK2Node_Variable* TestNode = TimelineVarNodes[It];
if(TestNode && (OldName == TestNode->GetVarName()))
{
UEdGraphPin* TestPin = TestNode->FindPin(OldName);
if(TestPin && (UTimelineComponent::StaticClass() == TestPin->PinType.PinSubCategoryObject.Get()))
{
TestNode->Modify();
if(TestNode->VariableReference.IsSelfContext())
{
TestNode->VariableReference.SetSelfMember(NewName);
}
else
{
//TODO:
UClass* ParentClass = TestNode->VariableReference.GetMemberParentClass((UClass*)nullptr);
TestNode->VariableReference.SetExternalMember(NewName, ParentClass);
}
TestPin->Modify();
TestPin->PinName = NewName;
}
}
}
Blueprint->Timelines.Remove(Template);
UObject* ExistingObject = StaticFindObject(nullptr, Template->GetOuter(), *NewTemplateName, true);
if (ExistingObject != Template && ExistingObject != nullptr)
{
ExistingObject->Rename(*MakeUniqueObjectName(ExistingObject->GetOuter(), ExistingObject->GetClass(), ExistingObject->GetFName()).ToString());
}
Template->Rename(*NewTemplateName, Template->GetOuter(), REN_None);
Blueprint->Timelines.Add(Template);
// Validate child blueprints and adjust variable names to avoid a potential name collision
FBlueprintEditorUtils::ValidateBlueprintChildVariables(Blueprint, NewName);
// Refresh references and flush editors
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
bRenamed = true;
}
}
return bRenamed;
}
//////////////////////////////////////////////////////////////////////////
// LevelScriptBlueprint
bool FBlueprintEditorUtils::FindReferencesToActorFromLevelScript(ULevelScriptBlueprint* LevelScriptBlueprint, AActor* InActor, TArray<UK2Node*>& ReferencedToActors)
{
if (LevelScriptBlueprint == nullptr)
{
return false;
}
TArray<UEdGraph*> AllGraphs;
LevelScriptBlueprint->GetAllGraphs(AllGraphs);
for(TArray<UEdGraph*>::TConstIterator it(AllGraphs); it; ++it)
{
const UEdGraph* CurrentGraph = *it;
TArray<UK2Node*> GraphNodes;
CurrentGraph->GetNodesOfClass(GraphNodes);
for(UK2Node* Node : GraphNodes)
{
if(Node != nullptr && Node->GetReferencedLevelActor() == InActor )
{
ReferencedToActors.Add(Node);
}
}
}
return ReferencedToActors.Num() > 0;
}
void FBlueprintEditorUtils::ReplaceAllActorRefrences(ULevelScriptBlueprint* InLevelScriptBlueprint, AActor* InOldActor, AActor* InNewActor)
{
bool bBlueprintModified = false;
auto BlueprintModify = [&bBlueprintModified, InLevelScriptBlueprint]()
{
if (!bBlueprintModified)
{
InLevelScriptBlueprint->Modify();
FBlueprintEditorUtils::MarkBlueprintAsModified(InLevelScriptBlueprint);
bBlueprintModified = true;
}
};
// Literal nodes are the common "get" type nodes and need to be updated with the new object reference
TArray< UK2Node_Literal* > LiteralNodes;
FBlueprintEditorUtils::GetAllNodesOfClass(InLevelScriptBlueprint, LiteralNodes);
for( UK2Node_Literal* LiteralNode : LiteralNodes )
{
if(LiteralNode->GetObjectRef() == InOldActor)
{
BlueprintModify();
LiteralNode->Modify();
LiteralNode->SetObjectRef(InNewActor);
LiteralNode->ReconstructNode();
}
}
// Actor Bound Events reference the actors as well and need to be updated
TArray< UK2Node_ActorBoundEvent* > ActorEventNodes;
FBlueprintEditorUtils::GetAllNodesOfClass(InLevelScriptBlueprint, ActorEventNodes);
for( UK2Node_ActorBoundEvent* ActorEventNode : ActorEventNodes )
{
if(ActorEventNode->GetReferencedLevelActor() == InOldActor)
{
BlueprintModify();
ActorEventNode->Modify();
ActorEventNode->EventOwner = InNewActor;
ActorEventNode->ReconstructNode();
}
}
}
void FBlueprintEditorUtils::ModifyActorReferencedGraphNodes(ULevelScriptBlueprint* LevelScriptBlueprint, const AActor* InActor)
{
TArray<UEdGraph*> AllGraphs;
LevelScriptBlueprint->GetAllGraphs(AllGraphs);
for(TArray<UEdGraph*>::TConstIterator it(AllGraphs); it; ++it)
{
const UEdGraph* CurrentGraph = *it;
TArray<UK2Node*> GraphNodes;
CurrentGraph->GetNodesOfClass(GraphNodes);
for( TArray<UK2Node*>::TIterator NodeIt(GraphNodes); NodeIt; ++NodeIt )
{
UK2Node* CurrentNode = *NodeIt;
if( CurrentNode != nullptr && CurrentNode->GetReferencedLevelActor() == InActor )
{
CurrentNode->Modify();
}
}
}
}
void FBlueprintEditorUtils::FindActorsThatReferenceActor( AActor* InActor, TArray<UClass*>& InClassesToIgnore, TArray<AActor*>& OutReferencingActors )
{
// Iterate all actors in the same world as InActor
for ( FActorIterator ActorIt(InActor->GetWorld()); ActorIt; ++ActorIt )
{
AActor* CurrentActor = *ActorIt;
if (( CurrentActor ) && ( CurrentActor != InActor ))
{
bool bShouldIgnore = false;
// Ignore Actors that aren't in the same level as InActor - cross level references are not allowed, so it's safe to ignore.
if ( !CurrentActor->IsInLevel(InActor->GetLevel() ) )
{
bShouldIgnore = true;
}
// Ignore Actors if they are of a type we were instructed to ignore.
for ( int32 IgnoreIndex = 0; IgnoreIndex < InClassesToIgnore.Num() && !bShouldIgnore; IgnoreIndex++ )
{
if ( CurrentActor->IsA( InClassesToIgnore[IgnoreIndex] ) )
{
bShouldIgnore = true;
}
}
if ( !bShouldIgnore )
{
// Get all references from CurrentActor and see if any are the Actor we're searching for
TArray<UObject*> References;
FReferenceFinder Finder( References );
Finder.FindReferences( CurrentActor );
if ( References.Contains( InActor ) )
{
OutReferencingActors.Add( CurrentActor );
}
}
}
}
};
class FActorMapReferenceProcessor : public FSimpleReferenceProcessorBase
{
TArray<UObject*> PotentiallyReferencedActors;
TMap<AActor*, TArray<AActor*>>& ReferencingActors;
public:
FActorMapReferenceProcessor(UWorld* InWorld, TArray<UObject*>& OutPotentialReferencerObjects, const TArray<UClass*>& ClassesToIgnore, TMap<AActor*, TArray<AActor*>>& ReferencingActors)
: ReferencingActors(ReferencingActors)
{
// Collect all actors in the world
for (FActorIterator ActorIt(InWorld); ActorIt; ++ActorIt)
{
if (AActor* CurrentActor = *ActorIt)
{
bool bShouldIgnore = false;
// Ignore actors if they belong to a class that's being ignored
for (UClass* ClassToIgnore : ClassesToIgnore)
{
if (CurrentActor->IsA(ClassToIgnore))
{
bShouldIgnore = true;
break;
}
}
OutPotentialReferencerObjects.Add(Cast<UObject>(CurrentActor));
// Collect all child elements of the actor and add them as potential referencer objects
TArray<FTypedElementHandle> ChildElementHandles;
UTypedElementRegistry* Registry = UTypedElementRegistry::GetInstance();
TTypedElement<ITypedElementHierarchyInterface> ElementHierarchyHandle = Registry->GetElement<ITypedElementHierarchyInterface>(UEngineElementsLibrary::AcquireEditorActorElementHandle(CurrentActor));
ElementHierarchyHandle.GetChildElements(ChildElementHandles, true);
// This is intentionally constructed in a way that will recursively get all child elements, by adding
// to the array of handles while iterating it. Don't try to change to a range-based loop
for (int i = 0; i < ChildElementHandles.Num(); ++i)
{
FTypedElementHandle ChildElementHandle = ChildElementHandles[i];
if (ITypedElementHierarchyInterface* ChildElementHierarchyInterface = Registry->GetElementInterface<ITypedElementHierarchyInterface>(ChildElementHandle))
{
ChildElementHierarchyInterface->GetChildElements(ChildElementHandle, ChildElementHandles, true);
}
if (ITypedElementObjectInterface* ChildElementObjectInterface = Registry->GetElementInterface<ITypedElementObjectInterface>(ChildElementHandle))
{
if (UObject* ChildObject = ChildElementObjectInterface->GetObject(ChildElementHandle))
{
OutPotentialReferencerObjects.Add(ChildObject);
}
}
}
if (bShouldIgnore)
{
continue;
}
PotentiallyReferencedActors.Add(CurrentActor);
}
}
}
FORCEINLINE_DEBUGGABLE void HandleTokenStreamObjectReference(FGCArrayStruct& ObjectsToSerializeStruct, UObject* ReferencingObject, UObject*& Object, UE::GC::FTokenId, EGCTokenType, bool)
{
if (!ReferencingObject)
{
ReferencingObject = ObjectsToSerializeStruct.GetReferencingObject();
}
if (!Object || !ReferencingObject || Object == ReferencingObject || !Object->IsA<AActor>())
{
return;
}
AActor* Actor = CastChecked<AActor>(Object);
if (!PotentiallyReferencedActors.Contains(Actor))
{
return;
}
// Ignore references from child objects
if (ReferencingObject->IsInOuter(Object))
{
return;
}
// The object itself if it's an actor, or the actor that contains it (if that exists)
if (AActor* ReferencingActor = ReferencingObject->IsA<AActor>() ? CastChecked<AActor>(ReferencingObject) : ReferencingObject->GetTypedOuter<AActor>())
{
// Don't record more than one reference from the same actor
ReferencingActors.FindOrAdd(Actor).AddUnique(ReferencingActor);
}
}
};
void FBlueprintEditorUtils::GetActorReferenceMap(UWorld* InWorld, TArray<UClass*>& InClassesToIgnore, TMap<AActor*, TArray<AActor*>>& OutReferencingActors)
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintEditorUtils::GetActorReferenceMap);
TArray<UObject*> InitialObjects;
FActorMapReferenceProcessor Processor(InWorld, /* out */ InitialObjects, InClassesToIgnore, OutReferencingActors);
UE::GC::FWorkerContext Context;
Context.SetInitialObjectsUnpadded(InitialObjects);
CollectReferences(Processor, Context);
}
void FBlueprintEditorUtils::FixLevelScriptActorBindings(ALevelScriptActor* LevelScriptActor, const ULevelScriptBlueprint* ScriptBlueprint)
{
if( ScriptBlueprint->BlueprintType != BPTYPE_LevelScript )
{
return;
}
UPackage* ActorPackage = LevelScriptActor->GetOutermost();
UPackage* BlueprintPkg = ScriptBlueprint->GetOutermost();
// the nodes in the Blueprint are going to be bound to actors within the same
// (level) package, they're the actors in the editor; if LevelScriptActor
// doesn't belong to that package, then it is likely a copy (for PIE), this guard
// prevents us from cross-binding instantiated (PIE) actors to editor objects
if (ActorPackage != BlueprintPkg)
{
return;
}
TArray<UEdGraph*> AllGraphs;
ScriptBlueprint->GetAllGraphs(AllGraphs);
// Iterate over all graphs, and find all bound event nodes
for( TArray<UEdGraph*>::TConstIterator GraphIt(AllGraphs); GraphIt; ++GraphIt )
{
TArray<UK2Node_ActorBoundEvent*> BoundEvents;
(*GraphIt)->GetNodesOfClass(BoundEvents);
for( TArray<UK2Node_ActorBoundEvent*>::TConstIterator NodeIt(BoundEvents); NodeIt; ++NodeIt )
{
UK2Node_ActorBoundEvent* EventNode = *NodeIt;
// For each bound event node, verify that we have an entry point in the LSA, and add a delegate to the target
if( EventNode && EventNode->EventOwner )
{
const FName TargetFunction = EventNode->CustomFunctionName;
// Check to make sure the level scripting actor actually has the function defined
if( LevelScriptActor->FindFunction(TargetFunction) )
{
// Grab the MC delegate we need to add to
FMulticastDelegateProperty* TargetDelegate = EventNode->GetTargetDelegateProperty();
if( TargetDelegate != nullptr &&
TargetDelegate->GetOwnerClass() &&
EventNode->EventOwner->GetClass()->IsChildOf(TargetDelegate->GetOwnerClass()))
{
// Create the delegate, and add it if it doesn't already exist
FScriptDelegate Delegate;
Delegate.BindUFunction(LevelScriptActor, TargetFunction);
TargetDelegate->AddDelegate(MoveTemp(Delegate), EventNode->EventOwner);
}
}
}
}
}
}
void FBlueprintEditorUtils::ListPackageContents(UPackage* Package, FOutputDevice& Ar)
{
Ar.Logf(TEXT("Package %s contains:"), *Package->GetName());
for (FThreadSafeObjectIterator ObjIt; ObjIt; ++ObjIt)
{
if (ObjIt->GetOuter() == Package)
{
Ar.Logf(TEXT(" %s (flags 0x%X)"), *ObjIt->GetFullName(), (int32)ObjIt->GetFlags());
}
}
}
bool FBlueprintEditorUtils::KismetDiagnosticExec(const TCHAR* InStream, FOutputDevice& Ar)
{
const TCHAR* Str = InStream;
if (FParse::Command(&Str, TEXT("FindBadBlueprintReferences")))
{
// Collect garbage first to remove any false positives
CollectGarbage( GARBAGE_COLLECTION_KEEPFLAGS );
UPackage* TransientPackage = GetTransientPackage();
// Unique blueprints/classes that contain badness
TSet<UObject*> ObjectsContainingBadness;
TSet<UPackage*> BadPackages;
// Run thru every object in the world
for (FThreadSafeObjectIterator ObjectIt; ObjectIt; ++ObjectIt)
{
UObject* TestObj = *ObjectIt;
// If the test object is itself transient, there is no concern
CA_SUPPRESS(28182); // https://connect.microsoft.com/VisualStudio/feedback/details/3082622
if (TestObj->HasAnyFlags(RF_Transient))
{
continue;
}
// Look for a containing scope (either a blueprint or a class)
UObject* OuterScope = nullptr;
for (UObject* TestOuter = TestObj; (TestOuter != nullptr) && (OuterScope == nullptr); TestOuter = TestOuter->GetOuter())
{
if (UClass* OuterClass = Cast<UClass>(TestOuter))
{
if (OuterClass->ClassGeneratedBy != nullptr)
{
OuterScope = OuterClass;
}
}
else if (UBlueprint* OuterBlueprint = Cast<UBlueprint>(TestOuter))
{
OuterScope = OuterBlueprint;
}
}
if ((OuterScope != nullptr) && !OuterScope->IsIn(TransientPackage))
{
// Find all references
TArray<UObject*> ReferencedObjects;
FReferenceFinder ObjectReferenceCollector(ReferencedObjects, nullptr, false, false, false, false);
ObjectReferenceCollector.FindReferences(TestObj);
for (UObject* ReferencedObj : ReferencedObjects)
{
// Ignore any references inside the outer blueprint or class; they're intrinsically safe
if (!ReferencedObj->IsIn(OuterScope))
{
// If it's a public reference, that's fine
if (!ReferencedObj->HasAnyFlags(RF_Public))
{
// It's a private reference outside of the parent object; not good!
Ar.Logf(TEXT("%s has a reference to %s outside of it's container %s"),
*TestObj->GetFullName(),
*ReferencedObj->GetFullName(),
*OuterScope->GetFullName()
);
ObjectsContainingBadness.Add(OuterScope);
BadPackages.Add(OuterScope->GetOutermost());
}
}
}
}
}
// Report all the bad outers as text dumps so the exact property can be identified
Ar.Logf(TEXT("Summary of assets containing objects that have bad references"));
for (UObject* BadObj : ObjectsContainingBadness)
{
Ar.Logf(TEXT("\n\nObject %s referenced private objects outside of it's container asset inappropriately"), *BadObj->GetFullName());
UBlueprint* Blueprint = Cast<UBlueprint>(BadObj);
if (Blueprint == nullptr)
{
if (UClass* Class = Cast<UClass>(BadObj))
{
Blueprint = CastChecked<UBlueprint>(Class->ClassGeneratedBy);
if (Blueprint->GeneratedClass == Class)
{
Ar.Logf(TEXT(" => GeneratedClass of %s"), *Blueprint->GetFullName());
}
else if (Blueprint->SkeletonGeneratedClass == Class)
{
Ar.Logf(TEXT(" => SkeletonGeneratedClass of %s"), *Blueprint->GetFullName());
}
else
{
Ar.Logf(TEXT(" => ***FALLEN BEHIND*** class generated by %s"), *Blueprint->GetFullName());
}
Ar.Logf(TEXT(" Has an associated CDO named %s"), *Class->GetDefaultObject()->GetFullName());
}
}
// Export the asset to text
if (true)
{
UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp));
FStringOutputDevice Archive;
const FExportObjectInnerContext Context;
UExporter::ExportToOutputDevice(&Context, BadObj, nullptr, Archive, TEXT("copy"), 0, /*PPF_IncludeTransient*/ /*PPF_ExportsNotFullyQualified|*/PPF_Copy, false, nullptr);
FString ExportedText = Archive;
Ar.Logf(TEXT("%s"), *ExportedText);
}
}
// Report the contents of the bad packages
for (UPackage* BadPackage : BadPackages)
{
Ar.Logf(TEXT("\nBad package %s contains:"), *BadPackage->GetName());
for (FThreadSafeObjectIterator ObjIt; ObjIt; ++ObjIt)
{
if (ObjIt->GetOuter() == BadPackage)
{
Ar.Logf(TEXT(" %s"), *ObjIt->GetFullName());
}
}
}
Ar.Logf(TEXT("\nFinished listing illegal private references"));
}
else if (FParse::Command(&Str, TEXT("ListPackageContents")))
{
if (UPackage* Package = FindPackage(nullptr, Str))
{
FBlueprintEditorUtils::ListPackageContents(Package, Ar);
}
else
{
Ar.Logf(TEXT("Failed to find package %s"), Str);
}
}
else if (FParse::Command(&Str, TEXT("RepairBlueprint")))
{
if (UBlueprint* Blueprint = FindFirstObject<UBlueprint>(Str, EFindFirstObjectOptions::None, ELogVerbosity::Warning, TEXT("RepairBlueprint")))
{
IKismetCompilerInterface& Compiler = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>(KISMET_COMPILER_MODULENAME);
Compiler.RecoverCorruptedBlueprint(Blueprint);
}
else
{
Ar.Logf(TEXT("Failed to find blueprint %s"), Str);
}
}
else if (FParse::Command(&Str, TEXT("ListOrphanClasses")))
{
UE_LOG(LogBlueprintDebug, Log, TEXT("--- LISTING ORPHANED CLASSES ---"));
for( TObjectIterator<UClass> it; it; ++it )
{
UClass* CurrClass = *it;
if( CurrClass->ClassGeneratedBy != nullptr && CurrClass->GetOutermost() != GetTransientPackage() )
{
if( UBlueprint* GeneratingBP = Cast<UBlueprint>(CurrClass->ClassGeneratedBy) )
{
if( CurrClass != GeneratingBP->GeneratedClass && CurrClass != GeneratingBP->SkeletonGeneratedClass )
{
UE_LOG(LogBlueprintDebug, Log, TEXT(" - %s"), *CurrClass->GetFullName());
}
}
}
}
return true;
}
else if (FParse::Command(&Str, TEXT("ListRootSetObjects")))
{
UE_LOG(LogBlueprintDebug, Log, TEXT("--- LISTING ROOTSET OBJ ---"));
for( FThreadSafeObjectIterator it; it; ++it )
{
UObject* CurrObj = *it;
if( CurrObj->IsRooted() )
{
UE_LOG(LogBlueprintDebug, Log, TEXT(" - %s"), *CurrObj->GetFullName());
}
}
}
else
{
return false;
}
return true;
}
void FBlueprintEditorUtils::OpenReparentBlueprintMenu( UBlueprint* Blueprint, const TSharedRef<SWidget>& ParentContent, const FOnClassPicked& OnPicked)
{
TArray< UBlueprint* > Blueprints;
Blueprints.Add( Blueprint );
OpenReparentBlueprintMenu( Blueprints, ParentContent, OnPicked );
}
class FBlueprintReparentFilter : public IClassViewerFilter
{
public:
/** All children of these classes will be included unless filtered out by another setting. */
TSet< const UClass* > AllowedChildrenOfClasses;
/** Classes to not allow any children of into the Class Viewer/Picker. */
TSet< const UClass* > DisallowedChildrenOfClasses;
/** Classes to never show in this class viewer. */
TSet< const UClass* > DisallowedClasses;
/** Will limit the results to only native classes */
bool bShowNativeOnly;
FBlueprintReparentFilter()
: bShowNativeOnly(false)
{}
virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override
{
// If it appears on the allowed child-of classes list (or there is nothing on that list)
// AND it is NOT on the disallowed child-of classes list
// AND it is NOT on the disallowed classes list
return InFilterFuncs->IfInChildOfClassesSet( AllowedChildrenOfClasses, InClass) != EFilterReturn::Failed &&
InFilterFuncs->IfInChildOfClassesSet(DisallowedChildrenOfClasses, InClass) != EFilterReturn::Passed &&
InFilterFuncs->IfInClassesSet(DisallowedClasses, InClass) != EFilterReturn::Passed &&
!InClass->HasAnyClassFlags(CLASS_Deprecated) &&
((bShowNativeOnly && InClass->HasAnyClassFlags(CLASS_Native)) || !bShowNativeOnly);
}
virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override
{
// If it appears on the allowed child-of classes list (or there is nothing on that list)
// AND it is NOT on the disallowed child-of classes list
// AND it is NOT on the disallowed classes list
return InFilterFuncs->IfInChildOfClassesSet( AllowedChildrenOfClasses, InUnloadedClassData) != EFilterReturn::Failed &&
InFilterFuncs->IfInChildOfClassesSet(DisallowedChildrenOfClasses, InUnloadedClassData) != EFilterReturn::Passed &&
InFilterFuncs->IfInClassesSet(DisallowedClasses, InUnloadedClassData) != EFilterReturn::Passed &&
!InUnloadedClassData->HasAnyClassFlags(CLASS_Deprecated) &&
((bShowNativeOnly && InUnloadedClassData->HasAnyClassFlags(CLASS_Native)) || !bShowNativeOnly);
}
};
TSharedRef<SWidget> FBlueprintEditorUtils::ConstructBlueprintParentClassPicker( const TArray< UBlueprint* >& Blueprints, const FOnClassPicked& OnPicked)
{
bool bIsActor = false;
bool bIsAnimBlueprint = false;
bool bIsLevelScriptActor = false;
bool bIsComponentBlueprint = false;
bool bIsEditorOnlyBlueprint = false;
bool bIsWidgetBlueprint = false;
TArray<UClass*> BlueprintClasses;
for( auto BlueprintIter = Blueprints.CreateConstIterator(); (!bIsActor && !bIsAnimBlueprint) && BlueprintIter; ++BlueprintIter )
{
const UBlueprint* Blueprint = *BlueprintIter;
bIsActor |= Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf( AActor::StaticClass() );
bIsAnimBlueprint |= Blueprint->IsA(UAnimBlueprint::StaticClass());
bIsLevelScriptActor |= Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf( ALevelScriptActor::StaticClass() );
bIsComponentBlueprint |= Blueprint->ParentClass && Blueprint->ParentClass->IsChildOf( UActorComponent::StaticClass() );
bIsEditorOnlyBlueprint |= IsEditorUtilityBlueprint(Blueprint);
bIsWidgetBlueprint = Blueprint->IsA(UBaseWidgetBlueprint::StaticClass());
if(Blueprint->GeneratedClass)
{
BlueprintClasses.Add(Blueprint->GeneratedClass);
}
}
// Fill in options
FClassViewerInitializationOptions Options;
Options.Mode = EClassViewerMode::ClassPicker;
Options.bShowBackgroundBorder = false;
TSharedPtr<FBlueprintReparentFilter> Filter = MakeShareable(new FBlueprintReparentFilter);
Options.ClassFilters.Add(Filter.ToSharedRef());
Options.ViewerTitleString = LOCTEXT("ReparentBlueprint", "Reparent blueprint");
// Only allow parenting to base blueprints.
Options.bIsBlueprintBaseOnly = true;
Options.bEditorClassesOnly = bIsEditorOnlyBlueprint;
// never allow parenting to Interface
Filter->DisallowedChildrenOfClasses.Add( UInterface::StaticClass() );
// never allow parenting to children of itself
for (UClass* BPClass : BlueprintClasses)
{
Filter->DisallowedChildrenOfClasses.Add(BPClass);
}
for ( UBlueprint* Blueprint : Blueprints )
{
Blueprint->GetReparentingRules(Filter->AllowedChildrenOfClasses, Filter->DisallowedChildrenOfClasses);
// Include a class viewer filter for imported namespaces if the class picker is being hosted in an editor context.
TSharedPtr<IToolkit> AssetEditor = FToolkitManager::Get().FindEditorForAsset(Blueprint);
if (AssetEditor.IsValid() && AssetEditor->IsBlueprintEditor())
{
TSharedPtr<IBlueprintEditor> BlueprintEditor = StaticCastSharedPtr<IBlueprintEditor>(AssetEditor);
TSharedPtr<IClassViewerFilter> ImportedClassViewerFilter = BlueprintEditor->GetImportedClassViewerFilter();
if (ImportedClassViewerFilter.IsValid())
{
Options.ClassFilters.AddUnique(ImportedClassViewerFilter.ToSharedRef());
}
}
}
if(bIsActor)
{
if(bIsLevelScriptActor)
{
// Don't allow conversion outside of the LevelScriptActor hierarchy
Filter->AllowedChildrenOfClasses.Add( ALevelScriptActor::StaticClass() );
Filter->bShowNativeOnly = true;
}
else
{
// Don't allow conversion outside of the Actor hierarchy
{
bool bHasAllowedActorClasses = false;
for (const UClass* Class : Filter->AllowedChildrenOfClasses)
{
if (Class && Class->IsChildOf(AActor::StaticClass()))
{
// This blueprint has already defined explicit actor classes to use
bHasAllowedActorClasses = true;
break;
}
}
if (!bHasAllowedActorClasses)
{
Filter->AllowedChildrenOfClasses.Add( AActor::StaticClass() );
}
}
// Don't allow non-LevelScriptActor->LevelScriptActor conversion
Filter->DisallowedChildrenOfClasses.Add( ALevelScriptActor::StaticClass() );
}
}
else if (bIsAnimBlueprint)
{
// If it's an anim blueprint, do not allow conversion to non anim
Filter->AllowedChildrenOfClasses.Add( UAnimInstance::StaticClass() );
}
else if(bIsComponentBlueprint)
{
// If it is a component blueprint, only allow classes under and including UActorComponent
Filter->AllowedChildrenOfClasses.Add( UActorComponent::StaticClass() );
}
else if (bIsEditorOnlyBlueprint && !bIsWidgetBlueprint)
{
Filter->DisallowedChildrenOfClasses.Add(UWidget::StaticClass());
}
else
{
Filter->DisallowedChildrenOfClasses.Add( AActor::StaticClass() );
}
for( const UBlueprint* Blueprint : Blueprints)
{
// don't allow making me my own parent!
if(Blueprint->GeneratedClass)
{
Filter->DisallowedClasses.Add(Blueprint->GeneratedClass);
}
// Disallow reparenting to any sub-class that has a different blueprint type.
// for example, reparenting AActor to AEditorUtilityActor is illegal because AActor uses UBlueprint while
// AEditorUtilityActor uses UEditorUtilityBlueprint
const IKismetCompilerInterface& KismetCompilerModule = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>("KismetCompiler");
if (Blueprint->ParentClass) // ParentClass is rarely null. Allow all children in that case.
{
KismetCompilerModule.GetSubclassesWithDifferingBlueprintTypes(Blueprint->ParentClass, Filter->DisallowedChildrenOfClasses);
}
}
return FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer").CreateClassViewer(Options, OnPicked);
}
void FBlueprintEditorUtils::OpenReparentBlueprintMenu( const TArray< UBlueprint* >& Blueprints, const TSharedRef<SWidget>& ParentContent, const FOnClassPicked& OnPicked)
{
if ( Blueprints.Num() == 0 )
{
return;
}
TSharedRef<SWidget> ClassPicker = ConstructBlueprintParentClassPicker(Blueprints, OnPicked);
TSharedRef<SBox> ClassPickerBox =
SNew(SBox)
.WidthOverride(280.0f)
.HeightOverride(400.0f)
[
SNew(SBorder)
.BorderImage(FAppStyle::GetBrush("Menu.Background"))
[
ClassPicker
]
];
// Show dialog to choose new parent class
FSlateApplication::Get().PushMenu(
ParentContent,
FWidgetPath(),
ClassPickerBox,
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect( FPopupTransitionEffect::ContextMenu),
true);
}
/** Filter class for ClassPicker handling allowed interfaces for a Blueprint */
class FBlueprintInterfaceFilter : public IClassViewerFilter
{
public:
/** All children of these classes will be included unless filtered out by another setting. */
TSet< const UClass* > AllowedChildrenOfClasses;
/** Classes to not allow any children of into the Class Viewer/Picker. */
TSet< const UClass* > DisallowedChildrenOfClasses;
/** Classes to never show in this class viewer. */
TSet< const UClass* > DisallowedClasses;
virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs ) override
{
// If it appears on the allowed child-of classes list (or there is nothing on that list)
// AND it is NOT on the disallowed child-of classes list
// AND it is NOT on the disallowed classes list
return InFilterFuncs->IfInChildOfClassesSet( AllowedChildrenOfClasses, InClass) != EFilterReturn::Failed &&
InFilterFuncs->IfInChildOfClassesSet(DisallowedChildrenOfClasses, InClass) != EFilterReturn::Passed &&
InFilterFuncs->IfInClassesSet(DisallowedClasses, InClass) != EFilterReturn::Passed &&
!InClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists) &&
FKismetEditorUtilities::IsClassABlueprintImplementableInterface(InClass);
}
virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InUnloadedClassData, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override
{
// Unloaded interfaces mean they must be Blueprint Interfaces
// If it appears on the allowed child-of classes list (or there is nothing on that list)
// AND it is NOT on the disallowed child-of classes list
// AND it is NOT on the disallowed classes list
return InFilterFuncs->IfInChildOfClassesSet( AllowedChildrenOfClasses, InUnloadedClassData) != EFilterReturn::Failed &&
InFilterFuncs->IfInChildOfClassesSet(DisallowedChildrenOfClasses, InUnloadedClassData) != EFilterReturn::Passed &&
InFilterFuncs->IfInClassesSet(DisallowedClasses, InUnloadedClassData) != EFilterReturn::Passed &&
!InUnloadedClassData->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists) &&
InUnloadedClassData->HasAnyClassFlags(CLASS_Interface);
}
};
TSharedRef<SWidget> FBlueprintEditorUtils::ConstructBlueprintInterfaceClassPicker( const TArray< UBlueprint* >& Blueprints, const FOnClassPicked& OnPicked)
{
TArray<UClass*> BlueprintClasses;
for (const UBlueprint* Blueprint : Blueprints)
{
if(Blueprint->GeneratedClass)
{
BlueprintClasses.Add(Blueprint->GeneratedClass);
}
}
// Fill in options
FClassViewerInitializationOptions Options;
Options.Mode = EClassViewerMode::ClassPicker;
Options.bShowBackgroundBorder = false;
TSharedPtr<FBlueprintInterfaceFilter> Filter = MakeShareable(new FBlueprintInterfaceFilter);
Options.ClassFilters.Add(Filter.ToSharedRef());
Options.ViewerTitleString = LOCTEXT("ImplementInterfaceBlueprint", "Implement Interface");
for (const UBlueprint* Blueprint : Blueprints)
{
// don't allow making me my own parent!
if(Blueprint->GeneratedClass)
{
Filter->DisallowedClasses.Add(Blueprint->GeneratedClass);
}
UClass const* const ParentClass = Blueprint->ParentClass;
// see if the parent class has any prohibited interfaces
if ((ParentClass != nullptr) && ParentClass->HasMetaData(FBlueprintMetadata::MD_ProhibitedInterfaces))
{
FString const& ProhibitedList = Blueprint->ParentClass->GetMetaData(FBlueprintMetadata::MD_ProhibitedInterfaces);
TArray<FString> ProhibitedInterfaceNames;
ProhibitedList.ParseIntoArray(ProhibitedInterfaceNames, TEXT(","), true);
// loop over all the prohibited interfaces
for (int32 ExclusionIndex = 0; ExclusionIndex < ProhibitedInterfaceNames.Num(); ++ExclusionIndex)
{
ProhibitedInterfaceNames[ExclusionIndex].TrimStartInline();
FString const& ProhibitedInterfaceName = ProhibitedInterfaceNames[ExclusionIndex].RightChop(1);
UClass* ProhibitedInterface = UClass::TryFindTypeSlow<UClass>(ProhibitedInterfaceName);
if(ProhibitedInterface)
{
Filter->DisallowedClasses.Add(ProhibitedInterface);
Filter->DisallowedChildrenOfClasses.Add(ProhibitedInterface);
}
}
}
// Do not allow adding interfaces that are already added to the Blueprint
TArray<UClass*> InterfaceClasses;
FindImplementedInterfaces(Blueprint, true, InterfaceClasses);
for(UClass* InterfaceClass : InterfaceClasses)
{
Filter->DisallowedClasses.Add(InterfaceClass);
}
// Include a class viewer filter for imported namespaces if the class picker is being hosted in an editor context
TSharedPtr<IToolkit> AssetEditor = FToolkitManager::Get().FindEditorForAsset(Blueprint);
if (AssetEditor.IsValid() && AssetEditor->IsBlueprintEditor())
{
TSharedPtr<IBlueprintEditor> BlueprintEditor = StaticCastSharedPtr<IBlueprintEditor>(AssetEditor);
TSharedPtr<IClassViewerFilter> ImportedClassViewerFilter = BlueprintEditor->GetImportedClassViewerFilter();
if (ImportedClassViewerFilter.IsValid())
{
Options.ClassFilters.AddUnique(ImportedClassViewerFilter.ToSharedRef());
}
}
}
// never allow parenting to children of itself
for (UClass* BPClass : BlueprintClasses)
{
Filter->DisallowedChildrenOfClasses.Add(BPClass);
}
return FModuleManager::LoadModuleChecked<FClassViewerModule>("ClassViewer").CreateClassViewer(Options, OnPicked);
}
/** Call PostEditChange() on any Actors that are based on this Blueprint */
void FBlueprintEditorUtils::PostEditChangeBlueprintActors(UBlueprint* Blueprint, bool bComponentEditChange)
{
if (Blueprint->GeneratedClass && Blueprint->GeneratedClass->IsChildOf(AActor::StaticClass()))
{
// Get the selected Actor set in the level editor context
bool bEditorSelectionChanged = false;
const USelection* CurrentEditorActorSelection = GEditor ? GEditor->GetSelectedActors() : nullptr;
const bool bIncludeDerivedClasses = false;
TArray<UObject*> MatchingBlueprintObjects;
GetObjectsOfClass(Blueprint->GeneratedClass, MatchingBlueprintObjects, bIncludeDerivedClasses, RF_ClassDefaultObject, EInternalObjectFlags::Garbage);
for (UObject* MatchingObj : MatchingBlueprintObjects)
{
// We know the class was derived from AActor because we checked the Blueprint->GeneratedClass.
AActor* Actor = static_cast<AActor*>(MatchingObj);
Actor->PostEditChange();
// Broadcast edit notification if necessary so that the level editor's detail panel is refreshed
bEditorSelectionChanged |= CurrentEditorActorSelection && CurrentEditorActorSelection->IsSelected(Actor);
}
// Broadcast edit notifications if necessary so that level editor details are refreshed (e.g. components tree)
if(bEditorSelectionChanged)
{
if(bComponentEditChange)
{
FLevelEditorModule& LevelEditor = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");
LevelEditor.BroadcastComponentsEdited();
}
}
}
// Let the blueprint thumbnail renderer know that a blueprint has been modified so it knows to reinstance components for visualization
FThumbnailRenderingInfo* RenderInfo = GUnrealEd->GetThumbnailManager()->GetRenderingInfo( Blueprint );
if ( RenderInfo != nullptr )
{
UBlueprintThumbnailRenderer* BlueprintThumbnailRenderer = Cast<UBlueprintThumbnailRenderer>(RenderInfo->Renderer);
if ( BlueprintThumbnailRenderer != nullptr )
{
BlueprintThumbnailRenderer->BlueprintChanged(Blueprint);
}
}
}
bool FBlueprintEditorUtils::IsPropertyPrivate(const FProperty* Property)
{
return Property->HasAnyPropertyFlags(CPF_NativeAccessSpecifierPrivate) || Property->GetBoolMetaData(FBlueprintMetadata::MD_Private);
}
FBlueprintEditorUtils::EPropertyWritableState FBlueprintEditorUtils::IsPropertyWritableInBlueprint(const UBlueprint* Blueprint, const FProperty* Property)
{
if (Property)
{
if (!Property->HasAnyPropertyFlags(CPF_BlueprintVisible))
{
return EPropertyWritableState::NotBlueprintVisible;
}
if (Property->HasAnyPropertyFlags(CPF_BlueprintReadOnly))
{
return EPropertyWritableState::BlueprintReadOnly;
}
if (Property->GetBoolMetaData(FBlueprintMetadata::MD_Private))
{
const UClass* OwningClass = Property->GetOwnerChecked<UClass>();
if (OwningClass->ClassGeneratedBy.Get() != Blueprint)
{
return EPropertyWritableState::Private;
}
}
}
return EPropertyWritableState::Writable;
}
FBlueprintEditorUtils::EPropertyReadableState FBlueprintEditorUtils::IsPropertyReadableInBlueprint(const UBlueprint* Blueprint, const FProperty* Property)
{
if (Property)
{
if (!Property->HasAnyPropertyFlags(CPF_BlueprintVisible))
{
return EPropertyReadableState::NotBlueprintVisible;
}
if (Property->GetBoolMetaData(FBlueprintMetadata::MD_Private))
{
const UClass* OwningClass = Property->GetOwnerChecked<UClass>();
if (OwningClass->ClassGeneratedBy.Get() != Blueprint)
{
return EPropertyReadableState::Private;
}
}
}
return EPropertyReadableState::Readable;
}
void FBlueprintEditorUtils::FindAndSetDebuggableBlueprintInstances()
{
TMap< UBlueprint*, TArray< AActor* > > BlueprintsNeedingInstancesToDebug;
// Find open blueprint editors that have no debug instances
UAssetEditorSubsystem* AssetEditorSubsystem = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>();
TArray<UObject*> EditedAssets = AssetEditorSubsystem->GetAllEditedAssets();
for(int32 i=0; i<EditedAssets.Num(); i++)
{
UBlueprint* Blueprint = Cast<UBlueprint>( EditedAssets[i] );
if( Blueprint != nullptr )
{
if (Blueprint->GetObjectPathToDebug().IsEmpty())
{
BlueprintsNeedingInstancesToDebug.FindOrAdd( Blueprint );
}
}
}
// If we have blueprints with no debug objects selected try to find a suitable on to debug
if( BlueprintsNeedingInstancesToDebug.Num() != 0 )
{
// This will only assign currently selected objects of the right type, otherwise leave on default behavior to break on any
USelection* Selected = GEditor->GetSelectedActors();
const bool bDisAllowDerivedTypes = false;
TArray< UBlueprint* > BlueprintsToRefresh;
for (TMap< UBlueprint*, TArray< AActor* > >::TIterator ObjIt(BlueprintsNeedingInstancesToDebug); ObjIt; ++ObjIt)
{
UBlueprint* EachBlueprint = ObjIt.Key();
bool bFoundItemToDebug = false;
if( Selected )
{
for (int32 iSelected = 0; iSelected < Selected->Num() ; iSelected++)
{
AActor* ObjectAsActor = Cast<AActor>( Selected->GetSelectedObject( iSelected ) );
UWorld* ActorWorld = ObjectAsActor ? ObjectAsActor->GetWorld() : nullptr;
if ((ActorWorld != nullptr) && (ActorWorld->WorldType != EWorldType::EditorPreview) && (ActorWorld->WorldType != EWorldType::Inactive))
{
if( IsObjectADebugCandidate(ObjectAsActor, EachBlueprint, true/*bInDisallowDerivedBlueprints*/ ) == true )
{
EachBlueprint->SetObjectBeingDebugged( ObjectAsActor );
bFoundItemToDebug = true;
BlueprintsToRefresh.Add( EachBlueprint );
break;
}
}
}
}
}
// Refresh all blueprint windows that we have made a change to the debugging selection of
for (int32 iRefresh = 0; iRefresh < BlueprintsToRefresh.Num() ; iRefresh++)
{
// Ensure its a blueprint editor !
TSharedPtr< IToolkit > FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(BlueprintsToRefresh[ iRefresh]);
if (FoundAssetEditor.IsValid() && FoundAssetEditor->IsBlueprintEditor())
{
TSharedPtr<IBlueprintEditor> BlueprintEditor = StaticCastSharedPtr<IBlueprintEditor>(FoundAssetEditor);
BlueprintEditor->RefreshEditors();
}
}
}
}
void FBlueprintEditorUtils::AnalyticsTrackNewNode( UEdGraphNode *NewNode )
{
UBlueprint* Blueprint = FindBlueprintForNodeChecked(NewNode);
TSharedPtr<IToolkit> FoundAssetEditor = FToolkitManager::Get().FindEditorForAsset(Blueprint);
if (FoundAssetEditor.IsValid() && FoundAssetEditor->IsBlueprintEditor())
{
TSharedPtr<IBlueprintEditor> BlueprintEditor = StaticCastSharedPtr<IBlueprintEditor>(FoundAssetEditor);
BlueprintEditor->AnalyticsTrackNodeEvent(Blueprint, NewNode, false);
}
}
bool FBlueprintEditorUtils::IsObjectADebugCandidate( AActor* InActorObject, UBlueprint* InBlueprint, bool bInDisallowDerivedBlueprints )
{
const bool bPassesFlags = !InActorObject->HasAnyFlags(RF_ClassDefaultObject) && IsValid(InActorObject);
bool bCanDebugThisObject = false;
if( bInDisallowDerivedBlueprints == true )
{
bCanDebugThisObject = InActorObject->GetClass()->ClassGeneratedBy == InBlueprint;
}
else if(InBlueprint->GeneratedClass)
{
bCanDebugThisObject = InActorObject->IsA( InBlueprint->GeneratedClass );
}
return bPassesFlags && bCanDebugThisObject;
}
bool FBlueprintEditorUtils::PropertyValueFromString(const FProperty* Property, const FString& StrValue, uint8* Container, UObject* OwningObject, int32 PortFlags)
{
return PropertyValueFromString_Direct(Property, StrValue, Property->ContainerPtrToValuePtr<uint8>(Container), OwningObject, PortFlags);
}
bool FBlueprintEditorUtils::PropertyValueFromString_Direct(const FProperty* Property, const FString& StrValue, uint8* DirectValue, UObject* OwningObject, int32 PortFlags)
{
bool bParseSucceeded = true;
if (!Property->IsA(FStructProperty::StaticClass()))
{
if (Property->IsA(FIntProperty::StaticClass()))
{
int32 IntValue = 0;
bParseSucceeded = FDefaultValueHelper::ParseInt(StrValue, IntValue);
CastFieldChecked<const FIntProperty>(Property)->SetPropertyValue(DirectValue, IntValue);
}
else if (Property->IsA(FInt64Property::StaticClass()))
{
int64 IntValue = 0;
bParseSucceeded = FDefaultValueHelper::ParseInt64(StrValue, IntValue);
CastFieldChecked<const FInt64Property>(Property)->SetPropertyValue(DirectValue, IntValue);
}
else if (Property->IsA(FFloatProperty::StaticClass()))
{
float FloatValue = 0.0f;
bParseSucceeded = FDefaultValueHelper::ParseFloat(StrValue, FloatValue);
CastFieldChecked<const FFloatProperty>(Property)->SetPropertyValue(DirectValue, FloatValue);
}
else if (Property->IsA(FDoubleProperty::StaticClass()))
{
double DoubleValue = 0.0;
bParseSucceeded = FDefaultValueHelper::ParseDouble(StrValue, DoubleValue);
CastFieldChecked<const FDoubleProperty>(Property)->SetPropertyValue(DirectValue, DoubleValue);
}
else if (const FByteProperty* ByteProperty = CastField<const FByteProperty>(Property))
{
int64 IntValue = 0;
if (const UEnum* Enum = ByteProperty->Enum)
{
if (StrValue.Len() < NAME_SIZE)
{
IntValue = Enum->GetValueByName(FName(*StrValue));
}
else
{
IntValue = INDEX_NONE;
}
bParseSucceeded = (INDEX_NONE != IntValue);
// If the parse did not succeed, clear out the int to keep the enum value valid
if (!bParseSucceeded)
{
IntValue = 0;
}
}
else
{
bParseSucceeded = FDefaultValueHelper::ParseInt64(StrValue, IntValue);
}
bParseSucceeded = bParseSucceeded && (IntValue <= 255) && (IntValue >= 0);
ByteProperty->SetPropertyValue(DirectValue, static_cast<uint8>(IntValue));
}
else if (const FEnumProperty* EnumProperty = CastField<const FEnumProperty>(Property))
{
bParseSucceeded = false;
if (const UEnum* Enum = EnumProperty->GetEnum())
{
int64 IntValue = INDEX_NONE;
if (StrValue.Len() < NAME_SIZE)
{
IntValue = Enum->GetValueByName(FName(*StrValue));
}
bParseSucceeded = (INDEX_NONE != IntValue);
// If the parse did not succeed, clear out the int to keep the enum value valid
if (!bParseSucceeded)
{
IntValue = 0;
}
bParseSucceeded = bParseSucceeded && (IntValue <= 255) && (IntValue >= 0);
EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(DirectValue, IntValue);
}
else
{
UE_LOG(
LogBlueprint,
Warning,
TEXT("Member 'Enum' of EnumProperty is nullptr, copy operation would fail. You could ignore this message if you moved the enum class. EnumProperty name:'%s', OwningObject: '%s', outer of OwningObject: '%s', outer of outer: '%s'"),
*EnumProperty->GetName(),
*GetNameSafe(OwningObject),
*GetNameSafe(OwningObject ? OwningObject->GetOuter() : nullptr),
*GetNameSafe(OwningObject ? OwningObject->GetOuter() ? OwningObject->GetOuter()->GetOuter(): nullptr : nullptr)
);
}
}
else if (Property->IsA(FStrProperty::StaticClass()))
{
CastFieldChecked<const FStrProperty>(Property)->SetPropertyValue(DirectValue, StrValue);
}
else if (Property->IsA(FBoolProperty::StaticClass()))
{
CastFieldChecked<const FBoolProperty>(Property)->SetPropertyValue(DirectValue, StrValue.ToBool());
}
else if (Property->IsA(FNameProperty::StaticClass()))
{
CastFieldChecked<const FNameProperty>(Property)->SetPropertyValue(DirectValue, FName(*StrValue));
}
else if (Property->IsA(FTextProperty::StaticClass()))
{
FStringOutputDevice ImportError;
const TCHAR* EndOfParsedBuff = Property->ImportText_Direct(*StrValue, DirectValue, OwningObject, PPF_SerializedAsImportText | PortFlags, &ImportError);
bParseSucceeded = EndOfParsedBuff && ImportError.IsEmpty();
}
else
{
// Empty array-like properties need to use "()" in order to import correctly (as array properties export comma separated within a set of brackets)
const TCHAR* const ValueToImport = (StrValue.IsEmpty() && (Property->IsA(FArrayProperty::StaticClass()) || Property->IsA(FMulticastDelegateProperty::StaticClass())))
? TEXT("()")
: *StrValue;
FStringOutputDevice ImportError;
const TCHAR* EndOfParsedBuff = Property->ImportText_Direct(*StrValue, DirectValue, OwningObject, PPF_SerializedAsImportText | PortFlags, &ImportError);
bParseSucceeded = EndOfParsedBuff && ImportError.IsEmpty();
}
}
else
{
static UScriptStruct* VectorStruct = TBaseStructure<FVector>::Get();
static UScriptStruct* RotatorStruct = TBaseStructure<FRotator>::Get();
static UScriptStruct* TransformStruct = TBaseStructure<FTransform>::Get();
static UScriptStruct* LinearColorStruct = TBaseStructure<FLinearColor>::Get();
const FStructProperty* StructProperty = CastFieldChecked<const FStructProperty>(Property);
// Struct properties must be handled differently, unfortunately. We only support FVector, FRotator, and FTransform
if (StructProperty->Struct == VectorStruct)
{
FVector V = FVector::ZeroVector;
bParseSucceeded = FDefaultValueHelper::ParseVector(StrValue, V);
Property->CopySingleValue(DirectValue, &V);
}
else if (StructProperty->Struct == RotatorStruct)
{
FRotator R = FRotator::ZeroRotator;
bParseSucceeded = FDefaultValueHelper::ParseRotator(StrValue, R);
Property->CopySingleValue(DirectValue, &R);
}
else if (StructProperty->Struct == TransformStruct)
{
FTransform T = FTransform::Identity;
bParseSucceeded = T.InitFromString(StrValue);
Property->CopySingleValue(DirectValue, &T);
}
else if (StructProperty->Struct == LinearColorStruct)
{
FLinearColor Color;
// Color form: "(R=%f,G=%f,B=%f,A=%f)"
bParseSucceeded = Color.InitFromString(StrValue);
Property->CopySingleValue(DirectValue, &Color);
}
else if (StructProperty->Struct)
{
const UScriptStruct* Struct = StructProperty->Struct;
const int32 StructSize = Struct->GetStructureSize() * StructProperty->ArrayDim;
StructProperty->InitializeValue(DirectValue);
ensure(1 == StructProperty->ArrayDim);
FStringOutputDevice ImportError;
const TCHAR* EndOfParsedBuff = StructProperty->ImportText_Direct(StrValue.IsEmpty() ? TEXT("()") : *StrValue, DirectValue, OwningObject, PPF_SerializedAsImportText | PortFlags, &ImportError);
bParseSucceeded &= EndOfParsedBuff && ImportError.IsEmpty();
}
}
return bParseSucceeded;
}
bool FBlueprintEditorUtils::PropertyValueToString(const FProperty* Property, const uint8* Container, FString& OutForm, UObject* OwningObject, int32 PortFlags)
{
return PropertyValueToString_Direct(Property, Property->ContainerPtrToValuePtr<const uint8>(Container), OutForm, OwningObject, PortFlags);
}
bool FBlueprintEditorUtils::PropertyValueToString_Direct(const FProperty* Property, const uint8* DirectValue, FString& OutForm, UObject* OwningObject, int32 PortFlags)
{
check(Property && DirectValue);
OutForm.Reset();
const FStructProperty* StructProperty = CastField<FStructProperty>(Property);
if (StructProperty)
{
static UScriptStruct* VectorStruct = TBaseStructure<FVector>::Get();
static UScriptStruct* RotatorStruct = TBaseStructure<FRotator>::Get();
static UScriptStruct* TransformStruct = TBaseStructure<FTransform>::Get();
static UScriptStruct* LinearColorStruct = TBaseStructure<FLinearColor>::Get();
// Struct properties must be handled differently, unfortunately. We only support FVector, FRotator, and FTransform
if (StructProperty->Struct == VectorStruct)
{
FVector Vector;
Property->CopySingleValue(&Vector, DirectValue);
OutForm = FString::Printf(TEXT("%f,%f,%f"), Vector.X, Vector.Y, Vector.Z);
}
else if (StructProperty->Struct == RotatorStruct)
{
FRotator Rotator;
Property->CopySingleValue(&Rotator, DirectValue);
OutForm = FString::Printf(TEXT("%f,%f,%f"), Rotator.Pitch, Rotator.Yaw, Rotator.Roll);
}
else if (StructProperty->Struct == TransformStruct)
{
FTransform Transform;
Property->CopySingleValue(&Transform, DirectValue);
OutForm = Transform.ToString();
}
else if (StructProperty->Struct == LinearColorStruct)
{
FLinearColor Color;
Property->CopySingleValue(&Color, DirectValue);
OutForm = Color.ToString();
}
}
bool bSucceeded = true;
if (OutForm.IsEmpty())
{
const uint8* DefaultValue = DirectValue;
bSucceeded = Property->ExportText_Direct(OutForm, DirectValue, DefaultValue, OwningObject, PPF_SerializedAsImportText | PortFlags);
}
return bSucceeded;
}
FName FBlueprintEditorUtils::GenerateUniqueGraphName(UObject* const InOuter, FString const& ProposedName)
{
FName UniqueGraphName(*ProposedName);
int32 CountPostfix = 1;
while (!FBlueprintEditorUtils::IsGraphNameUnique(InOuter, UniqueGraphName))
{
UniqueGraphName = FName(*FString::Printf(TEXT("%s%i"), *ProposedName, CountPostfix));
++CountPostfix;
}
return UniqueGraphName;
}
bool FBlueprintEditorUtils::CheckIfNodeConnectsToSelection(UEdGraphNode* InNode, const TSet<UEdGraphNode*>& InSelectionSet)
{
for (int32 PinIndex = 0; PinIndex < InNode->Pins.Num(); ++PinIndex)
{
UEdGraphPin* Pin = InNode->Pins[PinIndex];
if(Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)
{
for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex)
{
UEdGraphPin* LinkedToPin = Pin->LinkedTo[LinkIndex];
// The InNode, which is NOT in the new function, is checking if one of it's pins IS in the function, return true if it is. If not, check the node.
if(InSelectionSet.Contains(LinkedToPin->GetOwningNode()))
{
return true;
}
// Check the node recursively to see if it is connected back with selection.
if(CheckIfNodeConnectsToSelection(LinkedToPin->GetOwningNode(), InSelectionSet))
{
return true;
}
}
}
}
return false;
}
bool FBlueprintEditorUtils::CheckIfSelectionIsCycling(const TSet<UEdGraphNode*>& InSelectionSet, FCompilerResultsLog& InMessageLog)
{
for (UEdGraphNode* Node : InSelectionSet)
{
if (Node)
{
for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
{
UEdGraphPin* Pin = Node->Pins[PinIndex];
if(Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)
{
for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex)
{
UEdGraphPin* LinkedToPin = Pin->LinkedTo[LinkIndex];
// Check to see if this node, which is IN the selection, has any connections OUTSIDE the selection
// If it does, check to see if those nodes have any connections IN the selection
if(!InSelectionSet.Contains(LinkedToPin->GetOwningNode()))
{
if(CheckIfNodeConnectsToSelection(LinkedToPin->GetOwningNode(), InSelectionSet))
{
InMessageLog.Error(*LOCTEXT("DependencyCyleDetected_Error", "Dependency cycle detected, preventing node @@ from being scheduled").ToString(), LinkedToPin->GetOwningNode());
return true;
}
}
}
}
}
}
}
return false;
}
bool FBlueprintEditorUtils::IsPaletteActionReadOnly(TSharedPtr<FEdGraphSchemaAction> ActionIn, TSharedPtr<FBlueprintEditor> const BlueprintEditorIn)
{
check(BlueprintEditorIn.IsValid());
bool bIsReadOnly = false;
if(!BlueprintEditorIn->InEditingMode())
{
bIsReadOnly = true;
}
else
{
UBlueprint const* const BlueprintObj = BlueprintEditorIn->GetBlueprintObj();
if(ActionIn->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId())
{
FEdGraphSchemaAction_K2Graph* GraphAction = (FEdGraphSchemaAction_K2Graph*)ActionIn.Get();
// No graph is evidence of an overridable function, don't let the user modify it
if(GraphAction->EdGraph == nullptr)
{
bIsReadOnly = true;
}
else
{
// Graphs that cannot be deleted or re-named are read-only
if ( !(GraphAction->EdGraph->bAllowDeletion || GraphAction->EdGraph->bAllowRenaming) )
{
bIsReadOnly = true;
}
else
{
if(GraphAction->GraphType == EEdGraphSchemaAction_K2Graph::Function)
{
// Check if the function is an override
UFunction* OverrideFunc = FindUField<UFunction>(BlueprintObj->ParentClass, GraphAction->FuncName);
if ( OverrideFunc != nullptr )
{
bIsReadOnly = true;
}
}
else if(GraphAction->GraphType == EEdGraphSchemaAction_K2Graph::Interface)
{
// Interfaces cannot be renamed
bIsReadOnly = true;
}
}
}
}
else if(ActionIn->GetTypeId() == FEdGraphSchemaAction_K2Var::StaticGetTypeId())
{
FEdGraphSchemaAction_K2Var* VarAction = (FEdGraphSchemaAction_K2Var*)ActionIn.Get();
bIsReadOnly = true;
if( FBlueprintEditorUtils::FindNewVariableIndex(BlueprintObj, VarAction->GetVariableName()) != INDEX_NONE)
{
bIsReadOnly = false;
}
else if(BlueprintObj->FindTimelineTemplateByVariableName(VarAction->GetVariableName()))
{
bIsReadOnly = false;
}
else if(BlueprintEditorIn->CanAccessComponentsMode())
{
// Wasn't in the introduced variable list; try to find the associated SCS node
//@TODO: The SCS-generated variables should be in the variable list and have a link back;
// As it stands, you cannot do any metadata operations on a SCS variable, and you have to do icky code like the following
TArray<USCS_Node*> Nodes = BlueprintObj->SimpleConstructionScript->GetAllNodes();
for (TArray<USCS_Node*>::TConstIterator NodeIt(Nodes); NodeIt; ++NodeIt)
{
USCS_Node* CurrentNode = *NodeIt;
if (CurrentNode->GetVariableName() == VarAction->GetVariableName())
{
bIsReadOnly = false;
break;
}
}
}
}
else if(ActionIn->GetTypeId() == FEdGraphSchemaAction_K2Delegate::StaticGetTypeId())
{
FEdGraphSchemaAction_K2Delegate* DelegateAction = (FEdGraphSchemaAction_K2Delegate*)ActionIn.Get();
if( FBlueprintEditorUtils::FindNewVariableIndex(BlueprintObj, DelegateAction->GetDelegateName()) == INDEX_NONE)
{
bIsReadOnly = true;
}
}
else if (ActionIn->GetTypeId() == FEdGraphSchemaAction_K2Event::StaticGetTypeId())
{
FEdGraphSchemaAction_K2Event* EventAction = (FEdGraphSchemaAction_K2Event*)ActionIn.Get();
UK2Node* AssociatedNode = EventAction->NodeTemplate;
bIsReadOnly = (AssociatedNode == nullptr) || (!AssociatedNode->GetCanRenameNode());
}
else if (ActionIn->GetTypeId() == FEdGraphSchemaAction_K2InputAction::StaticGetTypeId())
{
bIsReadOnly = true;
}
}
return bIsReadOnly;
}
struct FUberGraphHelper
{
static void GetAll(const UBlueprint* Blueprint, TArray<UEdGraph*>& OutGraphs)
{
for (UEdGraph* UberGraph : Blueprint->UbergraphPages)
{
OutGraphs.Add(UberGraph);
UberGraph->GetAllChildrenGraphs(OutGraphs);
}
}
};
FName FBlueprintEditorUtils::GetFunctionNameFromClassByGuid(const UClass* InClass, const FGuid FunctionGuid)
{
TArray<UBlueprint*> Blueprints;
UBlueprint::GetBlueprintHierarchyFromClass(InClass, Blueprints);
for (int32 BPIndex = 0; BPIndex < Blueprints.Num(); ++BPIndex)
{
UBlueprint* Blueprint = Blueprints[BPIndex];
for (UEdGraph* FunctionGraph : Blueprint->FunctionGraphs)
{
if (FunctionGraph && FunctionGraph->GraphGuid == FunctionGuid)
{
return FunctionGraph->GetFName();
}
}
for (UEdGraph* FunctionGraph : Blueprint->DelegateSignatureGraphs)
{
if (FunctionGraph && FunctionGraph->GraphGuid == FunctionGuid)
{
const FString Name = FunctionGraph->GetName() + HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX;
return FName(*Name);
}
}
//FUNCTIONS BASED ON CUSTOM EVENTS:
TArray<UEdGraph*> UberGraphs;
FUberGraphHelper::GetAll(Blueprint, UberGraphs);
for (const UEdGraph* UberGraph : UberGraphs)
{
TArray<UK2Node_CustomEvent*> CustomEvents;
UberGraph->GetNodesOfClass(CustomEvents);
for (const UK2Node_CustomEvent* CustomEvent : CustomEvents)
{
if (!CustomEvent->bOverrideFunction && (CustomEvent->NodeGuid == FunctionGuid))
{
ensure(CustomEvent->CustomFunctionName != NAME_None);
return CustomEvent->CustomFunctionName;
}
}
}
}
return NAME_None;
}
bool FBlueprintEditorUtils::GetFunctionGuidFromClassByFieldName(const UClass* InClass, const FName FunctionName, FGuid& FunctionGuid)
{
if (FunctionName != NAME_None)
{
TArray<UBlueprint*> Blueprints;
UBlueprint::GetBlueprintHierarchyFromClass(InClass, Blueprints);
for (int32 BPIndex = 0; BPIndex < Blueprints.Num(); ++BPIndex)
{
UBlueprint* Blueprint = Blueprints[BPIndex];
for (UEdGraph* FunctionGraph : Blueprint->FunctionGraphs)
{
if (FunctionGraph && FunctionGraph->GetFName() == FunctionName)
{
FunctionGuid = FunctionGraph->GraphGuid;
return true;
}
}
FString BaseDelegateSignatureName = FunctionName.ToString();
if (BaseDelegateSignatureName.RemoveFromEnd(HEADER_GENERATED_DELEGATE_SIGNATURE_SUFFIX))
{
const FName GraphName(*BaseDelegateSignatureName);
for (UEdGraph* FunctionGraph : Blueprint->DelegateSignatureGraphs)
{
if (FunctionGraph && FunctionGraph->GetFName() == GraphName)
{
FunctionGuid = FunctionGraph->GraphGuid;
return true;
}
}
}
TArray<UEdGraph*> UberGraphs;
FUberGraphHelper::GetAll(Blueprint, UberGraphs);
for (const UEdGraph* UberGraph : UberGraphs)
{
TArray<UK2Node_Event*> EventNodes;
UberGraph->GetNodesOfClass(EventNodes);
for (const UK2Node_Event* EventNode : EventNodes)
{
if (EventNode->NodeGuid.IsValid())
{
if (const UK2Node_CustomEvent* CustomEventNode = Cast<UK2Node_CustomEvent>(EventNode))
{
if (CustomEventNode->CustomFunctionName == FunctionName)
{
FunctionGuid = EventNode->NodeGuid;
return true;
}
}
else if (EventNode->EventReference.GetMemberName() == FunctionName)
{
FunctionGuid = EventNode->NodeGuid;
return true;
}
}
}
}
}
}
FunctionGuid.Invalidate();
return false;
}
UK2Node_EditablePinBase* FBlueprintEditorUtils::GetEntryNode(const UEdGraph* InGraph)
{
UK2Node_EditablePinBase* Result = nullptr;
if (InGraph)
{
TArray<UK2Node_FunctionEntry*> EntryNodes;
InGraph->GetNodesOfClass(EntryNodes);
if (EntryNodes.Num() > 0)
{
if (EntryNodes[0]->IsEditable())
{
Result = EntryNodes[0];
}
}
else
{
TArray<UK2Node_Tunnel*> TunnelNodes;
InGraph->GetNodesOfClass(TunnelNodes);
if (TunnelNodes.Num() > 0)
{
// Iterate over the tunnel nodes, and try to find an entry and exit
for (int32 i = 0; i < TunnelNodes.Num(); i++)
{
UK2Node_Tunnel* Node = TunnelNodes[i];
// Composite nodes should never be considered for function entry / exit, since we're searching for a graph's terminals
if (Node->IsEditable() && !Node->IsA(UK2Node_Composite::StaticClass()))
{
if (Node->bCanHaveOutputs)
{
Result = Node;
break;
}
}
}
}
}
}
return Result;
}
void FBlueprintEditorUtils::GetEntryAndResultNodes(const UEdGraph* InGraph, TWeakObjectPtr<class UK2Node_EditablePinBase>& OutEntryNode, TWeakObjectPtr<class UK2Node_EditablePinBase>& OutResultNode)
{
if (InGraph)
{
// There are a few different potential configurations for editable graphs (FunctionEntry/Result, Tunnel Pairs, etc).
// Step through each case until we find one that matches what appears to be in the graph. This could be improved if
// we want to add more robust typing to the graphs themselves
// Case 1: Function Entry / Result Pair ------------------
TArray<UK2Node_FunctionEntry*> EntryNodes;
InGraph->GetNodesOfClass(EntryNodes);
if (EntryNodes.Num() > 0)
{
if (EntryNodes[0]->IsEditable())
{
OutEntryNode = EntryNodes[0];
// Find a result node
TArray<UK2Node_FunctionResult*> ResultNodes;
InGraph->GetNodesOfClass(ResultNodes);
UK2Node_FunctionResult* ResultNode = ResultNodes.Num() ? ResultNodes[0] : nullptr;
// Note: we assume that if the entry is editable, the result is too (since the entry node is guaranteed to be there on graph creation, but the result isn't)
if( ResultNode )
{
OutResultNode = ResultNode;
}
}
}
else
{
// Case 2: Tunnel Pair -----------------------------------
TArray<UK2Node_Tunnel*> TunnelNodes;
InGraph->GetNodesOfClass(TunnelNodes);
if (TunnelNodes.Num() > 0)
{
// Iterate over the tunnel nodes, and try to find an entry and exit
for (int32 i = 0; i < TunnelNodes.Num(); i++)
{
UK2Node_Tunnel* Node = TunnelNodes[i];
// Composite nodes should never be considered for function entry / exit, since we're searching for a graph's terminals
if (Node->IsEditable() && !Node->IsA(UK2Node_Composite::StaticClass()))
{
if (Node->bCanHaveOutputs)
{
ensure(!OutEntryNode.IsValid());
OutEntryNode = Node;
}
else if (Node->bCanHaveInputs)
{
ensure(!OutResultNode.IsValid());
OutResultNode = Node;
}
}
}
}
}
}
}
FKismetUserDeclaredFunctionMetadata* FBlueprintEditorUtils::GetGraphFunctionMetaData(const UEdGraph* InGraph)
{
if (InGraph)
{
UK2Node_EditablePinBase* FunctionEntryNode = GetEntryNode(InGraph);
if (UK2Node_FunctionEntry* TypedEntryNode = Cast<UK2Node_FunctionEntry>(FunctionEntryNode))
{
return &(TypedEntryNode->MetaData);
}
else if (UK2Node_Tunnel* TunnelNode = ExactCast<UK2Node_Tunnel>(FunctionEntryNode))
{
// Must be exactly a tunnel, not a macro instance
return &(TunnelNode->MetaData);
}
}
return nullptr;
}
void FBlueprintEditorUtils::ModifyFunctionMetaData(const UEdGraph* InGraph)
{
if (InGraph)
{
UK2Node_EditablePinBase* FunctionEntryNode = GetEntryNode(InGraph);
if (UK2Node_FunctionEntry* TypedEntryNode = Cast<UK2Node_FunctionEntry>(FunctionEntryNode))
{
TypedEntryNode->Modify();
}
else if (UK2Node_Tunnel* TunnelNode = ExactCast<UK2Node_Tunnel>(FunctionEntryNode))
{
TunnelNode->Modify();
}
}
}
FText FBlueprintEditorUtils::GetGraphDescription(const UEdGraph* InGraph)
{
if (FKismetUserDeclaredFunctionMetadata* MetaData = GetGraphFunctionMetaData(InGraph))
{
return MetaData->ToolTip;
}
return LOCTEXT( "NoGraphTooltip", "(None)" );
}
bool FBlueprintEditorUtils::CheckIfGraphHasLatentFunctions(UEdGraph* InGraph)
{
struct Local
{
static bool CheckIfGraphHasLatentFunctions(UEdGraph* InGraphToCheck, TArray<UEdGraph*>& InspectedGraphList)
{
UK2Node_EditablePinBase* EntryNode = GetEntryNode(InGraphToCheck);
UK2Node_Tunnel* TunnelNode = ExactCast<UK2Node_Tunnel>(EntryNode);
if(!TunnelNode)
{
// No tunnel, no metadata.
return false;
}
if(TunnelNode->MetaData.HasLatentFunctions != INDEX_NONE)
{
return TunnelNode->MetaData.HasLatentFunctions > 0;
}
else
{
// Add all graphs to the list of already inspected, this prevents circular inclusion issues.
InspectedGraphList.Add(InGraphToCheck);
for( const UEdGraphNode* Node : InGraphToCheck->Nodes )
{
// BP graphs can sometimes have non-UK2Nodes (eg: UEdGraphNode_Comment)
const UK2Node* K2Node = Cast<UK2Node>(Node);
if (K2Node)
{
if (K2Node->IsLatentForMacros())
{
TunnelNode->MetaData.HasLatentFunctions = 1;
return true;
}
else if (const UK2Node_MacroInstance* MacroInstanceNode = Cast<UK2Node_MacroInstance>(K2Node))
{
// Any macro graphs that haven't already been checked need to be checked for latent function calls
if (InspectedGraphList.Find(MacroInstanceNode->GetMacroGraph()) == INDEX_NONE)
{
if (CheckIfGraphHasLatentFunctions(MacroInstanceNode->GetMacroGraph(), InspectedGraphList))
{
TunnelNode->MetaData.HasLatentFunctions = 1;
return true;
}
}
}
else if (const UK2Node_Composite* CompositeNode = Cast<UK2Node_Composite>(K2Node))
{
// Any collapsed graphs that haven't already been checked need to be checked for latent function calls
if (InspectedGraphList.Find(CompositeNode->BoundGraph) == INDEX_NONE)
{
if (CheckIfGraphHasLatentFunctions(CompositeNode->BoundGraph, InspectedGraphList))
{
TunnelNode->MetaData.HasLatentFunctions = 1;
return true;
}
}
}
}
}
TunnelNode->MetaData.HasLatentFunctions = 0;
return false;
}
}
};
TArray<UEdGraph*> InspectedGraphList;
return Local::CheckIfGraphHasLatentFunctions(InGraph, InspectedGraphList);
}
void FBlueprintEditorUtils::PostSetupObjectPinType(UBlueprint* InBlueprint, FBPVariableDescription& InOutVarDesc)
{
if ((InOutVarDesc.VarType.PinCategory == UEdGraphSchema_K2::PC_Object) || (InOutVarDesc.VarType.PinCategory == UEdGraphSchema_K2::PC_Interface))
{
if (InOutVarDesc.VarType.PinSubCategory == UEdGraphSchema_K2::PSC_Self)
{
InOutVarDesc.VarType.PinSubCategory = NAME_None;
InOutVarDesc.VarType.PinSubCategoryObject = *InBlueprint->GeneratedClass;
}
else if (!InOutVarDesc.VarType.PinSubCategoryObject.IsValid())
{
// Fall back to UObject if the given type is not valid. This can happen for example if a variable is removed from
// a Blueprint parent class along with the variable's type and the user then attempts to recreate the missing variable
// through a stale variable node's context menu in a child Blueprint graph.
InOutVarDesc.VarType.PinSubCategory = NAME_None;
InOutVarDesc.VarType.PinSubCategoryObject = UObject::StaticClass();
}
// if it's a PC_Object, then it should have an associated UClass object
check(InOutVarDesc.VarType.PinSubCategoryObject.IsValid());
const UClass* ClassObject = Cast<UClass>(InOutVarDesc.VarType.PinSubCategoryObject.Get());
check(ClassObject != nullptr);
if (ClassObject->IsChildOf(AActor::StaticClass()))
{
// prevent Actor variables from having default values (because Blueprint templates are library elements that can
// bridge multiple levels and different levels might not have the actor that the default is referencing).
InOutVarDesc.PropertyFlags |= CPF_DisableEditOnTemplate;
}
}
}
const FSlateBrush* FBlueprintEditorUtils::GetIconFromPin( const FEdGraphPinType& PinType, bool bIsLarge )
{
const FSlateBrush* IconBrush = FAppStyle::GetBrush(TEXT("Kismet.VariableList.TypeIcon"));
const UObject* PinSubObject = PinType.PinSubCategoryObject.Get();
if( PinType.IsArray() && PinType.PinCategory != UEdGraphSchema_K2::PC_Exec )
{
IconBrush = FAppStyle::GetBrush(TEXT("Kismet.VariableList.ArrayTypeIcon"));
}
else if (PinType.IsMap() && PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)
{
IconBrush = FAppStyle::GetBrush(TEXT("Kismet.VariableList.MapKeyTypeIcon"));
}
else if (PinType.IsSet() && PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)
{
if( bIsLarge )
{
IconBrush = FAppStyle::GetBrush(TEXT("Kismet.VariableList.SetTypeIconLarge"));
}
else
{
IconBrush = FAppStyle::GetBrush(TEXT("Kismet.VariableList.SetTypeIcon"));
}
}
else if (PinType.PinCategory == UEdGraphSchema_K2::PC_MCDelegate)
{
IconBrush = FAppStyle::GetBrush(TEXT("GraphEditor.Delegate_16x"));
}
// FindObject will crash if called during save - and we have reported crashes here
// due to the save progress dialog invoking this function somehow
else if( PinSubObject && !UE::IsSavingPackage(nullptr))
{
UClass* VarClass = FindObject<UClass>(nullptr, *PinSubObject->GetFullName());
if( VarClass )
{
IconBrush = FSlateIconFinder::FindIconBrushForClass( VarClass );
}
}
return IconBrush;
}
const FSlateBrush* FBlueprintEditorUtils::GetSecondaryIconFromPin(const FEdGraphPinType& PinType)
{
if (PinType.IsMap() && PinType.PinCategory != UEdGraphSchema_K2::PC_Exec)
{
return FAppStyle::GetBrush(TEXT("Kismet.VariableList.MapValueTypeIcon"));
}
return nullptr;
}
bool FBlueprintEditorUtils::HasGetTypeHash(const FEdGraphPinType& PinType)
{
if(PinType.PinCategory == UEdGraphSchema_K2::PC_Boolean)
{
return false;
}
if (PinType.PinCategory == UEdGraphSchema_K2::PC_Text)
{
return false;
}
if (PinType.PinCategory != UEdGraphSchema_K2::PC_Struct)
{
// even object or class types can be hashed, no reason to investigate further
return true;
}
const UScriptStruct* StructType = Cast<const UScriptStruct>(PinType.PinSubCategoryObject.Get());
if( StructType )
{
return StructHasGetTypeHash(StructType);
}
return false;
}
bool FBlueprintEditorUtils::PropertyHasGetTypeHash(const FProperty* PropertyType)
{
return PropertyType->HasAllPropertyFlags(CPF_HasGetValueTypeHash);
}
bool FBlueprintEditorUtils::StructHasGetTypeHash(const UScriptStruct* StructType)
{
if (StructType->IsNative())
{
return StructType->GetCppStructOps() && StructType->GetCppStructOps()->HasGetTypeHash();
}
else
{
// if every member can be hashed (or is a FBoolProperty, which is specially
// handled by UScriptStruct::GetStructTypeHash) then we can hash the struct:
for (TFieldIterator<FProperty> It(StructType); It; ++It)
{
if (CastField<FBoolProperty>(*It))
{
continue;
}
else
{
if (!FBlueprintEditorUtils::PropertyHasGetTypeHash(*It) )
{
return false;
}
}
}
return true;
}
}
FText FBlueprintEditorUtils::GetFriendlyClassDisplayName(const UClass* Class)
{
if (Class != nullptr)
{
return Class->GetDisplayNameText();
}
else
{
return LOCTEXT("ClassIsNull", "None");
}
}
FString FBlueprintEditorUtils::GetClassNameWithoutSuffix(const UClass* Class)
{
if (Class != nullptr)
{
FString Result = Class->GetName();
if (Class->ClassGeneratedBy != nullptr)
{
Result.RemoveFromEnd(TEXT("_C"), ESearchCase::CaseSensitive);
}
return Result;
}
else
{
return LOCTEXT("ClassIsNull", "None").ToString();
}
}
FText FBlueprintEditorUtils::GetDeprecatedMemberMenuItemName(const FText& MemberName)
{
FFormatNamedArguments Args;
Args.Add(TEXT("MemberName"), MemberName);
return FText::Format(LOCTEXT("DeprecatedMemberMenuItemName", "{MemberName} (Deprecated)"), Args);
}
FText FBlueprintEditorUtils::GetDeprecatedMemberUsageNodeWarning(const FText& MemberName, const FText& DetailedMessage)
{
static FText UnknownName = LOCTEXT("UnknownDeprecatedMemberName", "[unknown]");
static FText DefaultMessage = LOCTEXT("DefaultDeprecatedMemberUsageDetails", "Please replace or remove it.");
FFormatNamedArguments Args;
Args.Add("MemberName", ensure(!MemberName.IsEmpty()) ? MemberName : UnknownName);
Args.Add("DetailedMessage", DetailedMessage.IsEmpty() ? DefaultMessage : DetailedMessage);
return FText::Format(LOCTEXT("DeprecatedMemberUsageNodeWarning", "@@: Usage of '{MemberName}' has been deprecated. {DetailedMessage}"), Args);
}
EEdGraphNodeDeprecationMessageType FBlueprintEditorUtils::GetDeprecatedMessageType(const FString& TypeString)
{
if (TypeString.Equals(TEXT("None"), ESearchCase::IgnoreCase))
{
return EEdGraphNodeDeprecationMessageType::None;
}
else if (TypeString.Equals(TEXT("Note"), ESearchCase::IgnoreCase))
{
return EEdGraphNodeDeprecationMessageType::Note;
}
// Default to warning
return EEdGraphNodeDeprecationMessageType::Warning;
}
UK2Node_FunctionResult* FBlueprintEditorUtils::FindOrCreateFunctionResultNode(UK2Node_EditablePinBase* InFunctionEntryNode)
{
UK2Node_FunctionResult* FunctionResult = nullptr;
if (InFunctionEntryNode)
{
UEdGraph* Graph = InFunctionEntryNode->GetGraph();
TArray<UK2Node_FunctionResult*> ResultNode;
if (Graph)
{
Graph->GetNodesOfClass(ResultNode);
}
if (Graph && ResultNode.Num() == 0)
{
FGraphNodeCreator<UK2Node_FunctionResult> ResultNodeCreator(*Graph);
FunctionResult = ResultNodeCreator.CreateNode();
const UEdGraphSchema_K2* Schema = Cast<const UEdGraphSchema_K2>(FunctionResult->GetSchema());
FunctionResult->NodePosX = InFunctionEntryNode->NodePosX + InFunctionEntryNode->NodeWidth + 256;
FunctionResult->NodePosY = InFunctionEntryNode->NodePosY;
FunctionResult->bIsEditable = true;
UEdGraphSchema_K2::SetNodeMetaData(FunctionResult, FNodeMetadata::DefaultGraphNode);
ResultNodeCreator.Finalize();
// Connect the function entry to the result node, if applicable
UEdGraphPin* ThenPin = Schema->FindExecutionPin(*InFunctionEntryNode, EGPD_Output);
UEdGraphPin* ReturnPin = Schema->FindExecutionPin(*FunctionResult, EGPD_Input);
if(ThenPin->LinkedTo.Num() == 0)
{
ThenPin->MakeLinkTo(ReturnPin);
}
else
{
// Bump the result node up a bit, so it's less likely to fall behind the node the entry is already connected to
FunctionResult->NodePosY -= 100;
}
}
else
{
FunctionResult = ResultNode[0];
}
}
return FunctionResult;
}
void FBlueprintEditorUtils::HandleDisableEditableWhenInherited(UObject* ModifiedObject, TArray<UObject*>& ArchetypeInstances)
{
for (int32 Index = ArchetypeInstances.Num() - 1; Index >= 0; --Index)
{
UObject* ArchetypeInstance = ArchetypeInstances[Index];
if (ArchetypeInstance != ModifiedObject)
{
UBlueprintGeneratedClass* BPGC = Cast<UBlueprintGeneratedClass>(ArchetypeInstance->GetOuter());
if (BPGC)
{
if (UInheritableComponentHandler* ICH = BPGC->GetInheritableComponentHandler(false))
{
ICH->RemoveOverridenComponentTemplate(ICH->FindKey(CastChecked<UActorComponent>(ArchetypeInstance)));
}
}
}
}
}
UClass* FBlueprintEditorUtils::GetNativeParent(const UBlueprint* BP)
{
UClass* Ret = BP->ParentClass;
while(Ret && !Ret->HasAnyClassFlags(CLASS_Native))
{
Ret = Ret->GetSuperClass();
}
return Ret;
}
UClass* FBlueprintEditorUtils::GetTypeForPin(const UEdGraphPin& Pin)
{
UClass* Ret = Cast<UClass>(Pin.PinType.PinSubCategoryObject.Get());
if(Ret == nullptr && Pin.PinType.PinSubCategory == UEdGraphSchema_K2::PSC_Self)
{
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForNodeChecked(Pin.GetOwningNode());
Ret = (Blueprint->GeneratedClass != NULL) ? Blueprint->GeneratedClass : Blueprint->ParentClass;
}
return Ret;
}
bool FBlueprintEditorUtils::ImplementsGetWorld(const UBlueprint* BP)
{
if(UClass* NativeParent = GetNativeParent(BP))
{
return NativeParent->GetDefaultObject()->ImplementsGetWorld();
}
return false;
}
struct FComponentInstancingDataUtils
{
// Recursively gathers properties that differ from class/struct defaults, and fills out the cooked property list structure.
static void RecursivePropertyGather(UStruct* InStruct, const uint8* DataPtr, const uint8* DefaultDataPtr, FBlueprintCookedComponentInstancingData& OutData)
{
for (FProperty* Property = InStruct->PropertyLink; Property; Property = Property->PropertyLinkNext)
{
// Skip editor-only properties since they won't be compiled in a non-editor configuration. Also skip transient and deprecated properties since they won't be serialized on save/duplicate.
if (!Property->IsEditorOnlyProperty()
&& !Property->HasAnyPropertyFlags(CPF_Transient | CPF_DuplicateTransient | CPF_NonPIEDuplicateTransient | CPF_Deprecated))
{
for (int32 Idx = 0; Idx < Property->ArrayDim; Idx++)
{
const uint8* PropertyValue = Property->ContainerPtrToValuePtr<uint8>(DataPtr, Idx);
const uint8* DefaultPropertyValue = Property->ContainerPtrToValuePtrForDefaults<uint8>(InStruct, DefaultDataPtr, Idx);
FBlueprintComponentChangedPropertyInfo ChangedPropertyInfo;
ChangedPropertyInfo.PropertyName = Property->GetFName();
ChangedPropertyInfo.ArrayIndex = Idx;
ChangedPropertyInfo.PropertyScope = InStruct;
if (FStructProperty* StructProperty = CastField<FStructProperty>(Property))
{
int32 NumChangedProperties = OutData.ChangedPropertyList.Num();
RecursivePropertyGather(StructProperty->Struct, PropertyValue, DefaultPropertyValue, OutData);
// Prepend the struct property only if there is at least one changed sub-property.
if (NumChangedProperties < OutData.ChangedPropertyList.Num())
{
OutData.ChangedPropertyList.Insert(ChangedPropertyInfo, NumChangedProperties);
}
}
else if (FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Property))
{
FScriptArrayHelper ArrayValueHelper(ArrayProperty, PropertyValue);
FScriptArrayHelper DefaultArrayValueHelper(ArrayProperty, DefaultPropertyValue);
int32 NumChangedProperties = OutData.ChangedPropertyList.Num();
FBlueprintComponentChangedPropertyInfo ChangedArrayPropertyInfo = ChangedPropertyInfo;
for (int32 ArrayValueIndex = 0; ArrayValueIndex < ArrayValueHelper.Num(); ++ArrayValueIndex)
{
ChangedArrayPropertyInfo.ArrayIndex = ArrayValueIndex;
const uint8* ArrayPropertyValue = ArrayValueHelper.GetRawPtr(ArrayValueIndex);
if (ArrayValueIndex < DefaultArrayValueHelper.Num())
{
const uint8* DefaultArrayPropertyValue = DefaultArrayValueHelper.GetRawPtr(ArrayValueIndex);
if (FStructProperty* InnerStructProperty = CastField<FStructProperty>(ArrayProperty->Inner))
{
int32 NumChangedArrayProperties = OutData.ChangedPropertyList.Num();
RecursivePropertyGather(InnerStructProperty->Struct, ArrayPropertyValue, DefaultArrayPropertyValue, OutData);
// Prepend the struct property only if there is at least one changed sub-property.
if (NumChangedArrayProperties < OutData.ChangedPropertyList.Num())
{
OutData.ChangedPropertyList.Insert(ChangedArrayPropertyInfo, NumChangedArrayProperties);
}
}
else if (!ArrayProperty->Inner->Identical(ArrayPropertyValue, DefaultArrayPropertyValue, PPF_None))
{
// Emit the index of the individual array value that differs from the default value
OutData.ChangedPropertyList.Add(ChangedArrayPropertyInfo);
}
}
else
{
// Emit the "end" of differences with the default value (signals that remaining values should be copied in full)
ChangedArrayPropertyInfo.PropertyName = NAME_None;
OutData.ChangedPropertyList.Add(ChangedArrayPropertyInfo);
// Don't need to record anything else.
break;
}
}
// Prepend the array property as changed only if the sizes differ and/or if we also wrote out any of the inner value as changed.
if (ArrayValueHelper.Num() != DefaultArrayValueHelper.Num() || NumChangedProperties < OutData.ChangedPropertyList.Num())
{
OutData.ChangedPropertyList.Insert(ChangedPropertyInfo, NumChangedProperties);
}
}
else if (!Property->Identical(PropertyValue, DefaultPropertyValue, PPF_None))
{
OutData.ChangedPropertyList.Add(ChangedPropertyInfo);
}
}
}
}
}
};
void FBlueprintEditorUtils::BuildComponentInstancingData(UActorComponent* ComponentTemplate, FBlueprintCookedComponentInstancingData& OutData, bool bUseTemplateArchetype)
{
if (ComponentTemplate)
{
UClass* ComponentTemplateClass = ComponentTemplate->GetClass();
const UObject* ComponentDefaults = bUseTemplateArchetype ? ComponentTemplate->GetArchetype() : ComponentTemplateClass->GetDefaultObject(false);
// Gather the set of properties that differ from the defaults.
OutData.ChangedPropertyList.Empty();
FComponentInstancingDataUtils::RecursivePropertyGather(ComponentTemplateClass, (uint8*)ComponentTemplate, (uint8*)ComponentDefaults, OutData);
// Flag that cooked data has been built and is now considered to be valid.
OutData.bHasValidCookedData = true;
}
}
namespace
{
// This structure provides the ability to find/update the nodes primary object. This must be specialized based
// on the type of the object being found/updated
template <typename TObjectType, bool bIsFind>
struct FFindOrUpdateNodeHelper
{
template <typename FindExisting>
static bool FindOrUpdateNode(UK2Node* Node, FindExisting& InFindExisting);
};
template <bool bIsFind>
struct FFindOrUpdateNodeHelper<UScriptStruct, bIsFind>
{
template <typename FindExisting>
static bool FindOrUpdateNode(UK2Node* Node, FindExisting InFindExisting)
{
// If this is a struct operation node operation on the changed struct we must reconstruct
if (UK2Node_StructOperation* StructOpNode = Cast<UK2Node_StructOperation>(Node))
{
if (UScriptStruct* StructInNode = Cast<UScriptStruct>(StructOpNode->StructType))
{
if (TOptional<UScriptStruct*> NewStructInNode = InFindExisting(StructInNode))
{
if (!bIsFind)
{
StructOpNode->StructType = *NewStructInNode;
}
return true;
}
}
}
return false;
}
};
template <bool bIsFind>
struct FFindOrUpdateNodeHelper<UEnum, bIsFind>
{
template <typename FindExisting>
static bool FindOrUpdateNode(UK2Node* Node, FindExisting InFindExisting)
{
if (INodeDependingOnEnumInterface* EnumInterface = Cast<INodeDependingOnEnumInterface>(Node))
{
if (UEnum* EnumInNode = EnumInterface->GetEnum())
{
if (TOptional<UEnum*> NewEnumInNode = InFindExisting(EnumInNode))
{
if (!bIsFind)
{
EnumInterface->ReloadEnum(*NewEnumInNode);
}
return true;
}
}
}
return false;
}
};
// Scan all the nodes looking for references to objects.
template <typename TObject, bool bIsFind, typename FindExisting>
void FindOrUpdateNodes(FBlueprintEditorUtils::FOnNodeFoundOrUpdated InOnNodeFoundOrUpdated, FindExisting InFindExisting)
{
for (TObjectIterator<UK2Node> It(RF_Transient | RF_ClassDefaultObject, /** bIncludeDerivedClasses */ true, /** InternalExcludeFlags */ EInternalObjectFlags::Garbage); It; ++It)
{
UK2Node* Node = *It;
if (Node && !Node->HasAnyFlags(RF_Transient) && IsValidChecked(Node))
{
bool bReconstruct = FFindOrUpdateNodeHelper<TObject, bIsFind>::FindOrUpdateNode(Node, InFindExisting);
// Look through the nodes pins and if any of them are split and the type of the split pin is a something we need to reconstruct
if (!bIsFind || !bReconstruct)
{
for (UEdGraphPin* Pin : Node->Pins)
{
if (TObject* Object = Cast<TObject>(Pin->PinType.PinSubCategoryObject.Get()))
{
if (TOptional<TObject*> NewObject = InFindExisting(Object))
{
bReconstruct = true;
if (bIsFind)
{
break;
}
Pin->PinType.PinSubCategoryObject = *NewObject;
}
}
}
}
if (bReconstruct)
{
UBlueprint* FoundBlueprint = Node->HasValidBlueprint() ? Node->GetBlueprint() : nullptr;
InOnNodeFoundOrUpdated(FoundBlueprint, Node);
}
}
}
}
}
void FBlueprintEditorUtils::FindScriptStructsInNodes(const TSet<UScriptStruct*>& Structs, FOnNodeFoundOrUpdated InOnNodeFoundOrUpdated)
{
if (Structs.Num() == 0)
{
return;
}
FindOrUpdateNodes<UScriptStruct, true>(InOnNodeFoundOrUpdated, [&Structs](UScriptStruct* ScriptStruct)
{
return Structs.Contains(ScriptStruct) ? TOptional(ScriptStruct) : TOptional<UScriptStruct*>();
}
);
}
void FBlueprintEditorUtils::FindEnumsInNodes(const TSet<UEnum*>& Enums, FOnNodeFoundOrUpdated InOnNodeFoundOrUpdated)
{
if (Enums.Num() == 0)
{
return;
}
FindOrUpdateNodes<UEnum, true>(InOnNodeFoundOrUpdated, [&Enums](UEnum* Enum)
{
return Enums.Contains(Enum) ? TOptional(Enum) : TOptional<UEnum*>();
}
);
}
void FBlueprintEditorUtils::UpdateScriptStructsInNodes(const TMap<UScriptStruct*, UScriptStruct*>& Structs, FOnNodeFoundOrUpdated InOnNodeFoundOrUpdated)
{
if (Structs.Num() == 0)
{
return;
}
FindOrUpdateNodes<UScriptStruct, false>(InOnNodeFoundOrUpdated, [&Structs] (UScriptStruct* ScriptStruct)
{
UScriptStruct* const* Found = Structs.Find(ScriptStruct);
return Found ? TOptional(*Found) : TOptional<UScriptStruct*>();
}
);
}
void FBlueprintEditorUtils::UpdateEnumsInNodes(const TMap<UEnum*, UEnum*>& Enums, FOnNodeFoundOrUpdated InOnNodeFoundOrUpdated)
{
if (Enums.Num() == 0)
{
return;
}
FindOrUpdateNodes<UEnum, false>(InOnNodeFoundOrUpdated, [&Enums](UEnum* Enum)
{
UEnum* const* Found = Enums.Find(Enum);
return Found ? TOptional(*Found) : TOptional<UEnum*>();
}
);
}
void FBlueprintEditorUtils::RecombineNestedSubPins(UK2Node* Node)
{
checkSlow(Node);
TArray<UEdGraphPin*> NestedSplitPins;
for (int32 i = Node->Pins.Num() - 1; i >= 0; --i)
{
UEdGraphPin* Pin = Node->Pins[i];
if (Pin->ParentPin != nullptr && Pin->ParentPin->ParentPin != nullptr && !Pin->bOrphanedPin)
{
NestedSplitPins.Add(Pin);
// If there was nothing connected to or changed about this pin, then skip it
if (Pin->LinkedTo.Num() > 0 || !Pin->DoesDefaultValueMatchAutogenerated())
{
// Otherwise add an orphan pin so warning/connections are not silently lost
UEdGraphPin* OrphanPin = Node->CreatePin(Pin->Direction, Pin->PinType, Pin->PinName);
OrphanPin->bOrphanedPin = true;
OrphanPin->bNotConnectable = true;
OrphanPin->DefaultValue = Pin->DefaultValue;
OrphanPin->DefaultObject = Pin->DefaultObject;
for (UEdGraphPin* OldLink : Pin->LinkedTo)
{
OrphanPin->MakeLinkTo(OldLink);
}
}
}
}
// Wait to recombine because otherwise we could end up combining pins that that haven't had their orphan created yet
const UEdGraphSchema* Schema = Node->GetSchema();
for (int32 i = NestedSplitPins.Num() - 1; i >= 0; --i)
{
Schema->RecombinePin(NestedSplitPins[i]);
}
}
static FAutoConsoleCommand AuditThreadSafeFunctions(
TEXT("bp.AuditThreadSafeFunctions"),
TEXT("Audit currently loaded thread safe functions. Writes results to the log."),
FConsoleCommandDelegate::CreateLambda([]()
{
UE_LOG(LogBlueprint, Display, TEXT("--- BEGIN audit all BlueprintThreadSafe functions ---"));
UE_LOG(LogBlueprint, Display, TEXT("Name, Path, Type, BPCallType"));
for (TObjectIterator<UFunction> It; It; ++It)
{
UFunction* Function = *It;
if (FBlueprintEditorUtils::HasFunctionBlueprintThreadSafeMetaData(Function))
{
const TCHAR* Native = Function->HasAnyFunctionFlags(FUNC_Native) ? TEXT("Native") : TEXT("Script");
const TCHAR* Purity = [Function]()
{
if (Function->HasAnyFunctionFlags(FUNC_BlueprintCallable))
{
return Function->HasAllFunctionFlags(FUNC_BlueprintPure | FUNC_BlueprintCallable) ? TEXT("Pure") : TEXT("Callable");
}
return TEXT("NotCallable");
}();
UE_LOG(LogBlueprint, Display, TEXT("%s, %s, %s, %s"), *Function->GetName(), *Function->GetPathName(), Native, Purity);
}
}
UE_LOG(LogBlueprint, Display, TEXT("--- END audit all BlueprintThreadSafe functions ---"));
}));
static FAutoConsoleCommand AuditFunctionCallsForBlueprint(
TEXT("bp.AuditFunctionCallsForBlueprint"),
TEXT("Audit all functions called by a specified blueprint. Single argument supplies the asset to audit. Writes results to the log."),
FConsoleCommandWithArgsDelegate::CreateLambda([](const TArray<FString>& InArgs)
{
if (InArgs.Num() != 1)
{
return;
}
// Find our Blueprint & load it
UBlueprint* Blueprint = LoadObject<UBlueprint>(nullptr, *InArgs[0]);
if (Blueprint == nullptr)
{
UE_LOG(LogBlueprint, Warning, TEXT("--- Could not load Blueprint %s ---"), *InArgs[0]);
return;
}
if (Blueprint->GeneratedClass == nullptr)
{
UE_LOG(LogBlueprint, Warning, TEXT("--- Blueprint %s as a null GeneratedClass ---"), *InArgs[0]);
return;
}
UE_LOG(LogBlueprint, Display, TEXT("--- BEGIN audit function calls for Blueprint %s ---"), *InArgs[0]);
UE_LOG(LogBlueprint, Display, TEXT("Name, Path, Type, BPCallType"));
struct FFunctionReferenceProcessor : public FSimpleReferenceProcessorBase
{
FORCEINLINE void HandleTokenStreamObjectReference(FGCArrayStruct& ObjectsToSerializeStruct, UObject* ReferencingObject, UObject*& Object, UE::GC::FTokenId, EGCTokenType TokenType, bool)
{
if (UFunction* Function = Cast<UFunction>(Object))
{
const TCHAR* Native = Function->HasAnyFunctionFlags(FUNC_Native) ? TEXT("Native") : TEXT("Script");
const TCHAR* Purity = [Function]()
{
if (Function->HasAnyFunctionFlags(FUNC_BlueprintCallable))
{
return Function->HasAllFunctionFlags(FUNC_BlueprintPure | FUNC_BlueprintCallable) ? TEXT("Pure") : TEXT("Callable");
}
return TEXT("NotCallable");
}();
UE_LOG(LogBlueprint, Display, TEXT("%s, %s, %s, %s"), *Function->GetName(), *Function->GetOuterUClass()->GetPathName(), Native, Purity);
}
}
} Processor;
TArray<UObject*> InitialObjects = {Blueprint->GeneratedClass};
UE::GC::FWorkerContext Context;
Context.SetInitialObjectsUnpadded(InitialObjects);
CollectReferences(Processor, Context);
UE_LOG(LogBlueprint, Display, TEXT("--- END audit all BlueprintThreadSafe functions ---"));
}));
bool FBlueprintEditorUtils::HasFunctionBlueprintThreadSafeMetaData(const UFunction* InFunction)
{
if(InFunction)
{
const bool bHasThreadSafeMetaData = InFunction->HasMetaData(FBlueprintMetadata::MD_ThreadSafe);
const bool bHasNotThreadSafeMetaData = InFunction->HasMetaData(FBlueprintMetadata::MD_NotThreadSafe);
const bool bClassHasThreadSafeMetaData = InFunction->GetOwnerClass() && InFunction->GetOwnerClass()->HasMetaData(FBlueprintMetadata::MD_ThreadSafe);
// Native functions need to just have the correct class/function metadata
const bool bThreadSafeNative = InFunction->HasAnyFunctionFlags(FUNC_Native) && (bHasThreadSafeMetaData || (bClassHasThreadSafeMetaData && !bHasNotThreadSafeMetaData));
// Script functions get their flag propagated from their entry point, and dont pay heed to class metadata
const bool bThreadSafeScript = !InFunction->HasAnyFunctionFlags(FUNC_Native) && bHasThreadSafeMetaData;
return bThreadSafeNative || bThreadSafeScript;
}
return false;
}
bool FBlueprintEditorUtils::ShouldOpenWithDataOnlyEditor(const UBlueprint* Blueprint)
{
return FBlueprintEditorUtils::IsDataOnlyBlueprint(Blueprint)
&& !FBlueprintEditorUtils::IsLevelScriptBlueprint(Blueprint)
&& !FBlueprintEditorUtils::IsInterfaceBlueprint(Blueprint)
&& !Blueprint->bForceFullEditor
&& !Blueprint->bIsNewlyCreated;
}
#undef LOCTEXT_NAMESPACE