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

1228 lines
42 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================
AnimationGraphSchema.cpp
=============================================================================*/
#include "AnimationGraphSchema.h"
#include "Animation/AnimationAsset.h"
#include "Animation/AnimBlueprint.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "ToolMenus.h"
#include "ObjectEditorUtils.h"
#include "K2Node.h"
#include "EdGraphSchema_K2_Actions.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "Animation/AnimSequence.h"
#include "AnimStateNode.h"
#include "Animation/BlendSpace.h"
#include "Animation/AimOffsetBlendSpace.h"
#include "Animation/AimOffsetBlendSpace1D.h"
#include "Animation/AnimNodeBase.h"
#include "AnimGraphNode_Base.h"
#include "AnimGraphNode_AssetPlayerBase.h"
#include "AnimGraphNode_BlendSpacePlayer.h"
#include "AnimGraphNode_ComponentToLocalSpace.h"
#include "AnimGraphNode_LocalToComponentSpace.h"
#include "AnimGraphNode_Root.h"
#include "AnimGraphNode_RotationOffsetBlendSpace.h"
#include "AnimGraphNode_SequencePlayer.h"
#include "Animation/PoseAsset.h"
#include "AnimGraphNode_PoseBlendNode.h"
#include "AnimGraphNode_PoseByName.h"
#include "AnimGraphCommands.h"
#include "K2Node_Knot.h"
#include "ScopedTransaction.h"
#include "Animation/AnimMontage.h"
#include "AnimGraphNode_LinkedInputPose.h"
#include "AnimGraphNode_LinkedAnimLayer.h"
#include "AnimGraphNode_LinkedAnimGraphBase.h"
#include "AnimGraphNode_RigidBody.h"
#include "AnimationBlendSpaceSampleGraph.h"
#include "GraphEditorDragDropAction.h"
#include "AnimationEditorUtils.h"
#include "Settings/AnimBlueprintSettings.h"
#define LOCTEXT_NAMESPACE "AnimationGraphSchema"
namespace UE::Anim::BP::Editor
{
bool IsSkeletonCompatible(const UAnimBlueprint* AnimBlueprint, const UAnimationAsset* Asset)
{
if (AnimBlueprint == nullptr)
{
return false; // No blueprint provided, cannot be compatible
}
if (AnimBlueprint->bIsTemplate)
{
return true; // Templates are always compatible
}
if (AnimBlueprint->TargetSkeleton == nullptr)
{
return false; // No target skeleton provided, cannot be compatible
}
return AnimBlueprint->TargetSkeleton->IsCompatibleForEditor(Asset->GetSkeleton());
}
}
/////////////////////////////////////////////////////
// FAnimationLayerDragDropAction
/** DragDropAction class for drag and dropping animation layers */
class ANIMGRAPH_API FAnimationLayerDragDropAction : public FGraphSchemaActionDragDropAction
{
public:
DRAG_DROP_OPERATOR_TYPE(FAnimationLayerDragDropAction, FGraphSchemaActionDragDropAction)
virtual FReply DroppedOnPanel(const TSharedRef< class SWidget >& Panel, const FVector2f& ScreenPosition, const FVector2f& GraphPosition, UEdGraph& Graph) override;
virtual FReply DroppedOnNode(const FVector2f& ScreenPosition, const FVector2f& GraphPosition) override;
virtual FReply DroppedOnPin(const FVector2f& ScreenPosition, const FVector2f& GraphPosition) override;
virtual FReply DroppedOnAction(TSharedRef<FEdGraphSchemaAction> Action) override;
virtual FReply DroppedOnCategory(FText Category) override;
virtual void HoverTargetChanged() override;
protected:
/** Constructor */
FAnimationLayerDragDropAction();
static TSharedRef<FAnimationLayerDragDropAction> New(TSharedPtr<FEdGraphSchemaAction> InAction, FName InFuncName, UAnimBlueprint* InRigBlueprint, UAnimationGraph* InRigGraph);
UAnimBlueprint* SourceAnimBlueprint;
UAnimationGraph* SourceAnimLayerGraph;
FName SourceFuncName;
friend class UAnimationGraphSchema;
};
FAnimationLayerDragDropAction::FAnimationLayerDragDropAction()
: FGraphSchemaActionDragDropAction()
, SourceAnimBlueprint(nullptr)
, SourceAnimLayerGraph(nullptr)
, SourceFuncName(NAME_None)
{
}
FReply FAnimationLayerDragDropAction::DroppedOnPanel(const TSharedRef< class SWidget >& Panel, const FVector2f& ScreenPosition, const FVector2f& GraphPosition, UEdGraph& Graph)
{
if (UAnimationGraph* TargetRigGraph = Cast<UAnimationGraph>(&Graph))
{
if (UAnimBlueprint* TargetAnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraph(TargetRigGraph)))
{
FGraphNodeCreator<UAnimGraphNode_LinkedAnimLayer> LinkedInputLayerNodeCreator(*TargetRigGraph);
UAnimGraphNode_LinkedAnimLayer* LinkedAnimLayerNode = LinkedInputLayerNodeCreator.CreateNode();
const FName GraphName = TargetRigGraph->GetFName();
LinkedAnimLayerNode->SetupFromLayerId(SourceFuncName);
LinkedInputLayerNodeCreator.Finalize();
LinkedAnimLayerNode->NodePosX = static_cast<int32>(GraphPosition.X);
LinkedAnimLayerNode->NodePosY = static_cast<int32>(GraphPosition.Y);
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(&Graph);
// See if we need to recompile skeleton after adding this node, or just mark dirty
if (LinkedAnimLayerNode->NodeCausesStructuralBlueprintChange())
{
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
}
else
{
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
}
}
}
return FReply::Unhandled();
}
FReply FAnimationLayerDragDropAction::DroppedOnNode(const FVector2f& ScreenPosition, const FVector2f& GraphPosition)
{
if (UEdGraphNode* TargetNode = GetHoveredNode())
{
if (UAnimGraphNode_LinkedAnimLayer* LinkedAnimLayer = Cast<UAnimGraphNode_LinkedAnimLayer>(TargetNode))
{
LinkedAnimLayer->Node.Layer = SourceFuncName;
return FReply::Handled();
}
}
return FReply::Unhandled();
}
FReply FAnimationLayerDragDropAction::DroppedOnPin(const FVector2f& ScreenPosition, const FVector2f& GraphPosition)
{
return FReply::Unhandled();
}
FReply FAnimationLayerDragDropAction::DroppedOnAction(TSharedRef<FEdGraphSchemaAction> Action)
{
return FReply::Unhandled();
}
FReply FAnimationLayerDragDropAction::DroppedOnCategory(FText Category)
{
return FReply::Unhandled();
}
void FAnimationLayerDragDropAction::HoverTargetChanged()
{
FGraphSchemaActionDragDropAction::HoverTargetChanged();
bDropTargetValid = true;
}
TSharedRef<FAnimationLayerDragDropAction> FAnimationLayerDragDropAction::New(TSharedPtr<FEdGraphSchemaAction> InAction, FName InFuncName, UAnimBlueprint* InAnimBlueprint, UAnimationGraph* InAnimationLayerGraph)
{
TSharedRef<FAnimationLayerDragDropAction> Action = MakeShareable(new FAnimationLayerDragDropAction);
Action->SourceAction = InAction;
Action->SourceAnimBlueprint = InAnimBlueprint;
Action->SourceAnimLayerGraph = InAnimationLayerGraph;
Action->SourceFuncName = InFuncName;
Action->Construct();
return Action;
}
/////////////////////////////////////////////////////
// UAnimationGraphSchema
UAnimationGraphSchema::UAnimationGraphSchema(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
PN_SequenceName = TEXT("Sequence");
NAME_NeverAsPin = TEXT("NeverAsPin");
NAME_PinHiddenByDefault = TEXT("PinHiddenByDefault");
NAME_PinShownByDefault = TEXT("PinShownByDefault");
NAME_AlwaysAsPin = TEXT("AlwaysAsPin");
NAME_OnEvaluate = TEXT("OnEvaluate");
NAME_CustomizeProperty = TEXT("CustomizeProperty");
DefaultEvaluationHandlerName = TEXT("EvaluateGraphExposedInputs");
}
FLinearColor UAnimationGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const
{
const bool bAdditive = PinType.PinSubCategory == TEXT("Additive");
if (UAnimationGraphSchema::IsLocalSpacePosePin(PinType))
{
if (bAdditive)
{
return FLinearColor(0.12, 0.60, 0.10);
}
else
{
return FLinearColor::White;
}
}
else if (UAnimationGraphSchema::IsComponentSpacePosePin(PinType))
{
//@TODO: Pick better colors
if (bAdditive)
{
return FLinearColor(0.12, 0.60, 0.60);
}
else
{
return FLinearColor(0.20f, 0.50f, 1.00f);
}
}
return Super::GetPinTypeColor(PinType);
}
EGraphType UAnimationGraphSchema::GetGraphType(const UEdGraph* TestEdGraph) const
{
return GT_Animation;
}
void UAnimationGraphSchema::CreateDefaultNodesForGraph(UEdGraph& Graph) const
{
// Create the result node
FGraphNodeCreator<UAnimGraphNode_Root> NodeCreator(Graph);
UAnimGraphNode_Root* ResultSinkNode = NodeCreator.CreateNode();
NodeCreator.Finalize();
SetNodeMetaData(ResultSinkNode, FNodeMetadata::DefaultGraphNode);
}
void UAnimationGraphSchema::HandleGraphBeingDeleted(UEdGraph& GraphBeingRemoved) const
{
if (UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(&GraphBeingRemoved))
{
// Look for state nodes that reference this graph
TArray<UAnimStateNodeBase*> StateNodes;
FBlueprintEditorUtils::GetAllNodesOfClassEx<UAnimStateNode>(Blueprint, StateNodes);
TSet<UAnimStateNodeBase*> NodesToDelete;
for (int32 i = 0; i < StateNodes.Num(); ++i)
{
UAnimStateNodeBase* StateNode = StateNodes[i];
if (StateNode->GetBoundGraph() == &GraphBeingRemoved)
{
NodesToDelete.Add(StateNode);
}
}
// Delete the node that owns us
ensure(NodesToDelete.Num() <= 1);
for (TSet<UAnimStateNodeBase*>::TIterator It(NodesToDelete); It; ++It)
{
UAnimStateNodeBase* NodeToDelete = *It;
FBlueprintEditorUtils::RemoveNode(Blueprint, NodeToDelete, true);
// Prevent re-entrancy here
NodeToDelete->ClearBoundGraph();
}
// Remove pose watches from nodes in this graph
if (UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(Blueprint))
{
AnimationEditorUtils::RemovePoseWatchesFromGraph(AnimBlueprint, &GraphBeingRemoved);
}
}
}
bool UAnimationGraphSchema::IsPosePin(const FEdGraphPinType& PinType)
{
return IsLocalSpacePosePin(PinType) || IsComponentSpacePosePin(PinType);
}
bool UAnimationGraphSchema::IsLocalSpacePosePin(const FEdGraphPinType& PinType)
{
UScriptStruct* PoseLinkStruct = FPoseLink::StaticStruct();
return (PinType.PinCategory == UAnimationGraphSchema::PC_Struct) && (PinType.PinSubCategoryObject == PoseLinkStruct);
}
bool UAnimationGraphSchema::IsComponentSpacePosePin(const FEdGraphPinType& PinType)
{
UScriptStruct* ComponentSpacePoseLinkStruct = FComponentSpacePoseLink::StaticStruct();
return (PinType.PinCategory == UAnimationGraphSchema::PC_Struct) && (PinType.PinSubCategoryObject == ComponentSpacePoseLinkStruct);
}
FEdGraphPinType UAnimationGraphSchema::MakeLocalSpacePosePin()
{
FEdGraphPinType PinType;
PinType.ResetToDefaults();
PinType.PinCategory = UAnimationGraphSchema::PC_Struct;
PinType.PinSubCategoryObject = FPoseLink::StaticStruct();
return PinType;
}
FEdGraphPinType UAnimationGraphSchema::MakeComponentSpacePosePin()
{
FEdGraphPinType PinType;
PinType.ResetToDefaults();
PinType.PinCategory = UAnimationGraphSchema::PC_Struct;
PinType.PinSubCategoryObject = FComponentSpacePoseLink::StaticStruct();
return PinType;
}
bool UAnimationGraphSchema::TryCreateConnection(UEdGraphPin* A, UEdGraphPin* B) const
{
UEdGraphPin* OutputPin = nullptr;
UEdGraphPin* InputPin = nullptr;
if(A->Direction == EEdGraphPinDirection::EGPD_Output)
{
OutputPin = A;
InputPin = B;
}
else
{
OutputPin = B;
InputPin = A;
}
check(OutputPin && InputPin);
UK2Node_Knot* OutputKnotNode = Cast<UK2Node_Knot>(OutputPin->GetOwningNode());
UK2Node_Knot* InputKnotNode = Cast<UK2Node_Knot>(InputPin->GetOwningNode());
bool bConnectionWithKnot = OutputKnotNode != nullptr || InputKnotNode != nullptr;
if(bConnectionWithKnot)
{
// Double check this is our "exec"-like line
bool bOutputIsPose = IsPosePin(OutputPin->PinType);
bool bInputIsPose = IsPosePin(InputPin->PinType);
bool bHavePosePin = bOutputIsPose || bInputIsPose;
bool bHaveWildPin = InputPin->PinType.PinCategory == PC_Wildcard || OutputPin->PinType.PinCategory == PC_Wildcard;
if((bOutputIsPose && bInputIsPose) || (bHavePosePin && bHaveWildPin))
{
// Ok this is a valid exec-like line, we need to kill any connections already on the output pin
OutputPin->BreakAllPinLinks();
}
}
if(Super::TryCreateConnection(A, B))
{
// Connection made - remove any bindings on the input pin
if(UAnimGraphNode_Base* AnimGraphNode = Cast<UAnimGraphNode_Base>(InputPin->GetOwningNode()))
{
// Compare FName without number to make sure we catch array properties that are split into multiple pins
FName ComparisonName = InputPin->GetFName();
ComparisonName.SetNumber(0);
AnimGraphNode->RemoveBindings(ComparisonName);
}
return true;
}
return false;
}
const FPinConnectionResponse UAnimationGraphSchema::DetermineConnectionResponseOfCompatibleTypedPins(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UEdGraphPin* InputPin, const UEdGraphPin* OutputPin) const
{
// Enforce a tree hierarchy; where poses can only have one output (parent) connection
if (IsPosePin(OutputPin->PinType) && IsPosePin(InputPin->PinType))
{
if ((OutputPin->LinkedTo.Num() > 0) || (InputPin->LinkedTo.Num() > 0))
{
const ECanCreateConnectionResponse ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_AB;
return FPinConnectionResponse(ReplyBreakOutputs, TEXT("Replace existing connections"));
}
}
// Fall back to standard K2 rules
return Super::DetermineConnectionResponseOfCompatibleTypedPins(PinA, PinB, InputPin, OutputPin);
}
bool UAnimationGraphSchema::ArePinsCompatible(const UEdGraphPin* PinA, const UEdGraphPin* PinB, const UClass* CallingContext, bool bIgnoreArray) const
{
// both are pose pin, but doesn't match type, then return false;
if (IsPosePin(PinA->PinType) && IsPosePin(PinB->PinType) && IsLocalSpacePosePin(PinA->PinType) != IsLocalSpacePosePin(PinB->PinType))
{
return false;
}
// Disallow pose pins connecting to wildcards (apart from reroute nodes)
if(IsPosePin(PinA->PinType) && PinB->PinType.PinCategory == PC_Wildcard)
{
return Cast<UK2Node_Knot>(PinB->GetOwningNode()) != nullptr;
}
else if(IsPosePin(PinB->PinType) && PinA->PinType.PinCategory == PC_Wildcard)
{
return Cast<UK2Node_Knot>(PinA->GetOwningNode()) != nullptr;
}
return Super::ArePinsCompatible(PinA, PinB, CallingContext, bIgnoreArray);
}
bool UAnimationGraphSchema::DoesSupportAnimNotifyActions() const
{
// Don't offer notify items in anim graph
return false;
}
void UAnimationGraphSchema::CreateFunctionGraphTerminators(UEdGraph& Graph, UClass* Class) const
{
if(const UAnimationGraphSchema* Schema = ExactCast<UAnimationGraphSchema>(Graph.GetSchema()))
{
const FName GraphName = Graph.GetFName();
// Get the function GUID from the most up-to-date class
FGuid GraphGuid;
FBlueprintEditorUtils::GetFunctionGuidFromClassByFieldName(FBlueprintEditorUtils::GetMostUpToDateClass(Class), GraphName, GraphGuid);
// Create a root node
FGraphNodeCreator<UAnimGraphNode_Root> RootNodeCreator(Graph);
UAnimGraphNode_Root* RootNode = RootNodeCreator.CreateNode();
RootNodeCreator.Finalize();
SetNodeMetaData(RootNode, FNodeMetadata::DefaultGraphNode);
UFunction* InterfaceToImplement = FindUField<UFunction>(Class, GraphName);
if (InterfaceToImplement)
{
// Propagate group from metadata
TArray<UAnimGraphNode_Root*> RootNodes;
Graph.GetNodesOfClass<UAnimGraphNode_Root>(RootNodes);
check(RootNodes.Num() == 1);
RootNodes[0]->Node.SetGroup(*FObjectEditorUtils::GetCategoryText(InterfaceToImplement).ToString());
int32 CurrentPoseIndex = 0;
for (TFieldIterator<FProperty> PropIt(InterfaceToImplement); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
FProperty* Param = *PropIt;
const bool bIsFunctionInput = !Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm);
if (bIsFunctionInput)
{
FEdGraphPinType PinType;
if(Schema->ConvertPropertyToPinType(Param, PinType))
{
// Create linked input pose for each pose pin type
if(UAnimationGraphSchema::IsPosePin(PinType))
{
FGraphNodeCreator<UAnimGraphNode_LinkedInputPose> LinkedInputNodeCreator(Graph);
UAnimGraphNode_LinkedInputPose* LinkedInputNode = LinkedInputNodeCreator.CreateNode();
LinkedInputNode->FunctionReference.SetExternalMember(GraphName, Class, GraphGuid);
LinkedInputNode->Node.Name = Param->GetFName();
LinkedInputNode->InputPoseIndex = CurrentPoseIndex;
LinkedInputNode->ReconstructNode();
SetNodeMetaData(LinkedInputNode, FNodeMetadata::DefaultGraphNode);
LinkedInputNodeCreator.Finalize();
CurrentPoseIndex++;
}
}
}
}
}
AutoArrangeInterfaceGraph(Graph);
}
else
{
Super::CreateFunctionGraphTerminators(Graph, Class);
}
}
bool UAnimationGraphSchema::CanShowDataTooltipForPin(const UEdGraphPin& Pin) const
{
return !IsPosePin(Pin.PinType) && UEdGraphSchema_K2::CanShowDataTooltipForPin(Pin);
}
bool UAnimationGraphSchema::CanGraphBeDropped(TSharedPtr<FEdGraphSchemaAction> InAction) const
{
if (!InAction.IsValid())
{
return false;
}
if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId())
{
FEdGraphSchemaAction_K2Graph* FuncAction = (FEdGraphSchemaAction_K2Graph*)InAction.Get();
if (UAnimationGraph* AnimGraph = Cast<UAnimationGraph>((UEdGraph*)FuncAction->EdGraph))
{
return true;
}
}
return false;
}
FReply UAnimationGraphSchema::BeginGraphDragAction(TSharedPtr<FEdGraphSchemaAction> InAction, const FPointerEvent& MouseEvent) const
{
if (!InAction.IsValid())
{
return FReply::Unhandled();
}
if (InAction->GetTypeId() == FEdGraphSchemaAction_K2Graph::StaticGetTypeId())
{
FEdGraphSchemaAction_K2Graph* FuncAction = (FEdGraphSchemaAction_K2Graph*)InAction.Get();
if (UAnimationGraph* AnimationLayerGraph = Cast<UAnimationGraph>((UEdGraph*)FuncAction->EdGraph))
{
if (UAnimBlueprint* TargetAnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraph(AnimationLayerGraph)))
{
return FReply::Handled().BeginDragDrop(FAnimationLayerDragDropAction::New(InAction, FuncAction->FuncName, TargetAnimBlueprint, AnimationLayerGraph));
}
}
}
return FReply::Unhandled();
}
bool UAnimationGraphSchema::SearchForAutocastFunction(const FEdGraphPinType& OutputPinType, const FEdGraphPinType& InputPinType, FName& TargetFunction, /*out*/ UClass*& FunctionOwner) const
{
TOptional<UEdGraphSchema_K2::FSearchForAutocastFunctionResults> Result = SearchForAutocastFunction(OutputPinType, InputPinType);
if (Result)
{
TargetFunction = Result->TargetFunction;
FunctionOwner = Result->FunctionOwner;
return true;
}
return false;
}
TOptional<UEdGraphSchema_K2::FSearchForAutocastFunctionResults> UAnimationGraphSchema::SearchForAutocastFunction(const FEdGraphPinType& OutputPinType, const FEdGraphPinType& InputPinType) const
{
TOptional<UEdGraphSchema_K2::FSearchForAutocastFunctionResults> Result;
if (IsComponentSpacePosePin(OutputPinType) && IsLocalSpacePosePin(InputPinType))
{
// Insert a Component To LocalSpace conversion
Result = UEdGraphSchema_K2::FSearchForAutocastFunctionResults{};
}
else if (IsLocalSpacePosePin(OutputPinType) && IsComponentSpacePosePin(InputPinType))
{
// Insert a Local To ComponentSpace conversion
Result = UEdGraphSchema_K2::FSearchForAutocastFunctionResults{};
}
else
{
Result = Super::SearchForAutocastFunction(OutputPinType, InputPinType);
}
return Result;
}
bool UAnimationGraphSchema::CreateAutomaticConversionNodeAndConnections(UEdGraphPin* PinA, UEdGraphPin* PinB) const
{
// Determine which pin is an input and which pin is an output
UEdGraphPin* InputPin = NULL;
UEdGraphPin* OutputPin = NULL;
if (!CategorizePinsByDirection(PinA, PinB, /*out*/ InputPin, /*out*/ OutputPin))
{
return false;
}
// Look for animation specific conversion operations
UK2Node* TemplateNode = NULL;
if (IsComponentSpacePosePin(OutputPin->PinType) && IsLocalSpacePosePin(InputPin->PinType))
{
TemplateNode = NewObject<UAnimGraphNode_ComponentToLocalSpace>();
}
else if (IsLocalSpacePosePin(OutputPin->PinType) && IsComponentSpacePosePin(InputPin->PinType))
{
TemplateNode = NewObject<UAnimGraphNode_LocalToComponentSpace>();
}
// Spawn the conversion node if it's specific to animation
if (TemplateNode != NULL)
{
UEdGraph* Graph = InputPin->GetOwningNode()->GetGraph();
FVector2D AverageLocation = CalculateAveragePositionBetweenNodes(InputPin, OutputPin);
UK2Node* ConversionNode = FEdGraphSchemaAction_K2NewNode::SpawnNodeFromTemplate<UK2Node>(Graph, TemplateNode, AverageLocation);
AutowireConversionNode(InputPin, OutputPin, ConversionNode);
return true;
}
else
{
// Give the regular conversions a shot
return Super::CreateAutomaticConversionNodeAndConnections(PinA, PinB);
}
}
bool IsAimOffsetBlendSpace(UBlendSpace* BlendSpace)
{
return BlendSpace->IsA(UAimOffsetBlendSpace::StaticClass()) ||
BlendSpace->IsA(UAimOffsetBlendSpace1D::StaticClass());
}
void UAnimationGraphSchema::SpawnNodeFromAsset(UAnimationAsset* Asset, const UE::Slate::FDeprecateVector2DParameter& GraphPosition, UEdGraph* Graph, UEdGraphPin* PinIfAvailable)
{
check(Graph);
check(Graph->GetSchema()->IsA(UAnimationGraphSchema::StaticClass()));
check(Asset);
UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraph(Graph));
const bool bSkelMatch = UE::Anim::BP::Editor::IsSkeletonCompatible(AnimBlueprint, Asset);
const bool bTypeMatch = (PinIfAvailable == nullptr) || UAnimationGraphSchema::IsLocalSpacePosePin(PinIfAvailable->PinType);
const bool bDirectionMatch = (PinIfAvailable == nullptr) || (PinIfAvailable->Direction == EGPD_Input);
if (bSkelMatch && bTypeMatch && bDirectionMatch)
{
FEdGraphSchemaAction_K2NewNode Action;
UClass* NewNodeClass = GetNodeClassForAsset(Asset->GetClass());
if (NewNodeClass)
{
check(NewNodeClass->IsChildOf(UAnimGraphNode_AssetPlayerBase::StaticClass()));
UAnimGraphNode_AssetPlayerBase* NewNode = NewObject<UAnimGraphNode_AssetPlayerBase>(GetTransientPackage(), NewNodeClass);
NewNode->SetAnimationAsset(Asset);
NewNode->CopySettingsFromAnimationAsset(Asset);
Action.NodeTemplate = NewNode;
Action.PerformAction(Graph, PinIfAvailable, GraphPosition);
}
}
}
void UAnimationGraphSchema::SpawnRigidBodyNodeFromAsset(UPhysicsAsset* Asset, const UE::Slate::FDeprecateVector2DParameter& GraphPosition, UEdGraph* Graph)
{
check(Graph);
check(Graph->GetSchema()->IsA(UAnimationGraphSchema::StaticClass()));
check(Asset);
FEdGraphSchemaAction_K2NewNode Action;
UAnimGraphNode_RigidBody* NewNode = NewObject<UAnimGraphNode_RigidBody>(GetTransientPackage());
NewNode->Node.OverridePhysicsAsset = Asset;
Action.NodeTemplate = NewNode;
Action.PerformAction(Graph, nullptr, GraphPosition);
}
void UAnimationGraphSchema::UpdateNodeWithAsset(UK2Node* K2Node, UAnimationAsset* Asset)
{
if (Asset != NULL)
{
if (UAnimGraphNode_AssetPlayerBase* AssetPlayerNode = Cast<UAnimGraphNode_AssetPlayerBase>(K2Node))
{
if (AssetPlayerNode->SupportsAssetClass(Asset->GetClass()) != EAnimAssetHandlerType::NotSupported)
{
const FScopedTransaction Transaction(LOCTEXT("UpdateNodeWithAsset", "Updating Node with Asset"));
AssetPlayerNode->Modify();
AssetPlayerNode->SetAnimationAsset(Asset);
K2Node->GetSchema()->ForceVisualizationCacheClear();
K2Node->ReconstructNode();
}
}
}
}
void UAnimationGraphSchema::DroppedAssetsOnGraph( const TArray<FAssetData>& Assets, const FVector2f& GraphPosition, UEdGraph* Graph ) const
{
if (Graph != NULL)
{
if (UAnimationAsset* AnimationAsset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets))
{
SpawnNodeFromAsset(AnimationAsset, GraphPosition, Graph, NULL);
}
else if (UPhysicsAsset* PhysicsAsset = FAssetData::GetFirstAsset<UPhysicsAsset>(Assets))
{
SpawnRigidBodyNodeFromAsset(PhysicsAsset, FDeprecateSlateVector2D(GraphPosition), Graph);
}
}
}
void UAnimationGraphSchema::DroppedAssetsOnNode(const TArray<FAssetData>& Assets, const FVector2f& GraphPosition, UEdGraphNode* Node) const
{
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
UK2Node* K2Node = Cast<UK2Node>(Node);
if ((Asset != NULL) && (K2Node!= NULL))
{
UpdateNodeWithAsset(K2Node, Asset);
}
}
void UAnimationGraphSchema::DroppedAssetsOnPin(const TArray<FAssetData>& Assets, const FVector2f& GraphPosition, UEdGraphPin* Pin) const
{
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
if ((Asset != NULL) && (Pin != NULL))
{
// Don't have access to bounding information for node, using fixed offset that should work for most cases.
const FVector2f FixedOffset(-250.0f, 50.0f);
SpawnNodeFromAsset(Asset, GraphPosition + FixedOffset, Pin->GetOwningNode()->GetGraph(), Pin);
}
}
void UAnimationGraphSchema::GetAssetsNodeHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphNode* HoverNode, FString& OutTooltipText, bool& OutOkIcon) const
{
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
if ((Asset == NULL) || (HoverNode == NULL) || !HoverNode->IsA(UAnimGraphNode_Base::StaticClass()))
{
OutTooltipText = TEXT("");
OutOkIcon = false;
return;
}
bool bCanPlayAsset = SupportNodeClassForAsset(Asset->GetClass(), HoverNode->GetClass());
// this one only should happen when there is an Anim Blueprint
UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(HoverNode));
const bool bSkelMatch = UE::Anim::BP::Editor::IsSkeletonCompatible(AnimBlueprint, Asset);
if (!bSkelMatch)
{
OutOkIcon = false;
OutTooltipText = LOCTEXT("SkeletonsNotCompatible", "Skeletons are not compatible").ToString();
}
else if (bCanPlayAsset)
{
OutOkIcon = true;
OutTooltipText = FText::Format(LOCTEXT("AssetNodeHoverMessage_Success", "Change node to play '{0}'"), FText::FromString(Asset->GetName())).ToString();
}
else
{
OutOkIcon = false;
OutTooltipText = FText::Format(LOCTEXT("AssetNodeHoverMessage_Fail", "Cannot play '{0}' on this node type"), FText::FromString(Asset->GetName())).ToString();
}
}
void UAnimationGraphSchema::GetAssetsPinHoverMessage(const TArray<FAssetData>& Assets, const UEdGraphPin* HoverPin, FString& OutTooltipText, bool& OutOkIcon) const
{
UAnimationAsset* Asset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets);
if ((Asset == NULL) || (HoverPin == NULL))
{
OutTooltipText = TEXT("");
OutOkIcon = false;
return;
}
// this one only should happen when there is an Anim Blueprint
UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForNode(HoverPin->GetOwningNode()));
const bool bSkelMatch = UE::Anim::BP::Editor::IsSkeletonCompatible(AnimBlueprint, Asset);
const bool bTypeMatch = UAnimationGraphSchema::IsLocalSpacePosePin(HoverPin->PinType);
const bool bDirectionMatch = HoverPin->Direction == EGPD_Input;
if (bSkelMatch && bTypeMatch && bDirectionMatch)
{
OutOkIcon = true;
OutTooltipText = FText::Format(LOCTEXT("AssetPinHoverMessage_Success", "Play {0} and feed to {1}"), FText::FromString(Asset->GetName()), FText::FromName(HoverPin->PinName)).ToString();
}
else
{
OutOkIcon = false;
OutTooltipText = LOCTEXT("AssetPinHoverMessage_Fail", "Type or direction mismatch; must be wired to a pose input").ToString();
}
}
void UAnimationGraphSchema::GetAssetsGraphHoverMessage(const TArray<FAssetData>& Assets, const UEdGraph* HoverGraph, FString& OutTooltipText, bool& OutOkIcon) const
{
if (UAnimationAsset* AnimationAsset = FAssetData::GetFirstAsset<UAnimationAsset>(Assets))
{
UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(FBlueprintEditorUtils::FindBlueprintForGraph(HoverGraph));
const bool bSkelMatch = UE::Anim::BP::Editor::IsSkeletonCompatible(AnimBlueprint, AnimationAsset);
if (!bSkelMatch)
{
OutOkIcon = false;
if(AnimBlueprint && AnimBlueprint->bIsTemplate)
{
OutTooltipText = LOCTEXT("TemplateNotAllowed", "Template animation blueprints cannot reference assets").ToString();
}
else
{
OutTooltipText = LOCTEXT("SkeletonsNotCompatible", "Skeletons are not compatible").ToString();
}
}
else if(UAnimMontage* Montage = FAssetData::GetFirstAsset<UAnimMontage>(Assets))
{
OutOkIcon = false;
OutTooltipText = LOCTEXT("NoMontagesInAnimGraphs", "Montages cannot be used in animation graphs").ToString();
}
else
{
OutOkIcon = true;
OutTooltipText = TEXT("");
}
}
else if(UPhysicsAsset* PhysicsAsset = FAssetData::GetFirstAsset<UPhysicsAsset>(Assets))
{
OutOkIcon = true;
OutTooltipText = TEXT("");
}
}
void UAnimationGraphSchema::GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
{
Super::GetContextMenuActions(Menu, Context);
if (const UAnimGraphNode_Base* AnimGraphNode = Cast<UAnimGraphNode_Base>(Context->Node))
{
{
// Node contextual actions
FToolMenuSection& Section = Menu->AddSection("AnimGraphSchemaNodeActions", LOCTEXT("AnimNodeActionsMenuHeader", "Anim Node Actions"));
if (GetDefault<UAnimBlueprintSettings>()->bAllowPoseWatches)
{
Section.AddMenuEntry(FAnimGraphCommands::Get().TogglePoseWatch);
}
Section.AddMenuEntry(FAnimGraphCommands::Get().HideUnboundPropertyPins);
}
if(Context->Pin && !IsPosePin(Context->Pin->PinType))
{
TSharedPtr<SWidget> BindingWidget = MakeBindingWidgetForPin({ const_cast<UAnimGraphNode_Base*>(AnimGraphNode) }, Context->Pin->GetFName(), false, true);
if(BindingWidget.IsValid())
{
FToolMenuSection& Section = Menu->AddSection("EdGraphSchemaPinActions");
Section.AddEntry(FToolMenuEntry::InitWidget("BindingWidget", BindingWidget.ToSharedRef(), LOCTEXT("BindingWidgetLabel", "Binding"), true));
}
}
}
}
void UAnimationGraphSchema::HideUnboundPropertyPins(UAnimGraphNode_LinkedAnimGraphBase* Node)
{
TArrayView<FOptionalPinFromProperty> OptionalPins = Node->CustomPinProperties;
for (FOptionalPinFromProperty& OptionalPin : OptionalPins)
{
FName PropertyName;
FProperty* Property = nullptr;
int32 PinIndex = 0;
Node->GetPinBindingInfo(OptionalPin.PropertyName, PropertyName, Property, PinIndex);
if (Node->IsPinUnlinkedUnboundAndUnset(OptionalPin.PropertyName.ToString(), EGPD_Input))
{
Node->SetCustomPinVisibility(false, PinIndex);
}
}
}
TSharedPtr<SWidget> UAnimationGraphSchema::MakeBindingWidgetForPin(const TArray<UAnimGraphNode_Base*>& InAnimGraphNodes, FName InPinName, bool bInOnGraphNode, TAttribute<bool> bInIsEnabled)
{
const UAnimGraphNode_Base* FirstNode = InAnimGraphNodes[0];
FProperty* PinProperty = nullptr;
int32 OptionalPinIndex = INDEX_NONE;
FName BindingName = NAME_None;
if(FirstNode && FirstNode->GetPinBindingInfo(InPinName, BindingName, PinProperty, OptionalPinIndex))
{
check(PinProperty);
check(OptionalPinIndex != INDEX_NONE);
check(BindingName != NAME_None);
const bool bPropertyIsOnFNode = FirstNode->GetFNodeProperty() != nullptr && (FirstNode->GetFNodeProperty()->Struct->IsChildOf(PinProperty->GetOwner<UScriptStruct>()));
UAnimGraphNode_Base::FAnimPropertyBindingWidgetArgs BindingArgs(InAnimGraphNodes, PinProperty, InPinName, BindingName, OptionalPinIndex);
BindingArgs.OnGetOptionalPins = UAnimGraphNode_Base::FAnimPropertyBindingWidgetArgs::FOnGetOptionalPins::CreateLambda([bPropertyIsOnFNode](UAnimGraphNode_Base* InNode, TArrayView<FOptionalPinFromProperty>& OutOptionalPins)
{
if(UAnimGraphNode_CustomProperty* CustomProperty = Cast<UAnimGraphNode_CustomProperty>(InNode))
{
if(bPropertyIsOnFNode)
{
OutOptionalPins = InNode->ShowPinForProperties;
}
else
{
OutOptionalPins = CustomProperty->CustomPinProperties;
}
}
else
{
OutOptionalPins = InNode->ShowPinForProperties;
}
});
BindingArgs.OnSetPinVisibility = UAnimGraphNode_Base::FAnimPropertyBindingWidgetArgs::FOnSetPinVisibility::CreateLambda([bPropertyIsOnFNode](UAnimGraphNode_Base* InNode, bool bInVisible, int32 InOptionalPinIndex)
{
if(UAnimGraphNode_CustomProperty* CustomProperty = Cast<UAnimGraphNode_CustomProperty>(InNode))
{
if(bPropertyIsOnFNode)
{
InNode->SetPinVisibility(bInVisible, InOptionalPinIndex);
}
else
{
CustomProperty->SetCustomPinVisibility(bInVisible, InOptionalPinIndex);
}
}
else
{
InNode->SetPinVisibility(bInVisible, InOptionalPinIndex);
}
});
// Only show 'always dynamic' for properties of the internal FAnimNode_Base
BindingArgs.bPropertyIsOnFNode = bPropertyIsOnFNode;
BindingArgs.bOnGraphNode = bInOnGraphNode;
// Wrap in a box to control the widget's enabled & visibility states
return SNew(SBox)
.IsEnabled(bInIsEnabled)
.Visibility_Lambda([BindingName, FirstNode, bInOnGraphNode]()
{
if(bInOnGraphNode)
{
if (FirstNode->HasBinding(BindingName))
{
return EVisibility::Visible;
}
return EVisibility::Collapsed;
}
return EVisibility::Visible;
})
[
UAnimGraphNode_Base::MakePropertyBindingWidget(BindingArgs)
];
}
return nullptr;
}
FText UAnimationGraphSchema::GetPinDisplayName(const UEdGraphPin* Pin) const
{
check(Pin != NULL);
FText DisplayName = Super::GetPinDisplayName(Pin);
if (UAnimGraphNode_Base* Node = Cast<UAnimGraphNode_Base>(Pin->GetOwningNode()))
{
FString ProcessedDisplayName = DisplayName.ToString();
Node->PostProcessPinName(Pin, ProcessedDisplayName);
DisplayName = FText::FromString(ProcessedDisplayName);
}
return DisplayName;
}
bool UAnimationGraphSchema::CanDuplicateGraph(UEdGraph* InSourceGraph) const
{
return InSourceGraph->GetFName() != UEdGraphSchema_K2::GN_AnimGraph && !InSourceGraph->IsA<UAnimationBlendSpaceSampleGraph>();
}
void UAnimationGraphSchema::GetGraphDisplayInformation(const UEdGraph& Graph, /*out*/ FGraphDisplayInfo& DisplayInfo) const
{
if (GetGraphType(&Graph) == GT_Animation)
{
DisplayInfo.PlainName = FText::FromString(Graph.GetName());
DisplayInfo.DisplayName = DisplayInfo.PlainName;
DisplayInfo.Tooltip = Graph.GetFName() == UEdGraphSchema_K2::GN_AnimGraph ?
LOCTEXT("GraphTooltip_AnimGraph", "Graph used to blend together different animations.") :
LOCTEXT("GraphTooltip_AnimGraphLayer", "Layer used to organize blending into sub-graphs.");
if(!Graph.bAllowDeletion)
{
// Might be from an interface, so check
if(UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(&Graph))
{
TSubclassOf<UInterface> Interface;
auto FindInterfaceForGraph = [&Blueprint, &Graph](TSubclassOf<UInterface>& OutInterface)
{
for(const FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces)
{
for(UEdGraph* InterfaceGraph : InterfaceDesc.Graphs)
{
if(InterfaceGraph == &Graph)
{
OutInterface = InterfaceDesc.Interface;
return true;
}
}
}
return false;
};
if(FindInterfaceForGraph(Interface))
{
DisplayInfo.Tooltip = FText::Format(LOCTEXT("GraphTooltip_AnimGraphInterface", "Layer inherited from interface '{0}'."), FText::FromString(Interface.Get()->GetName()));
}
}
}
DisplayInfo.DocLink = TEXT("Shared/Editors/BlueprintEditor/GraphTypes");
DisplayInfo.DocExcerptName = TEXT("AnimGraph");
}
else
{
Super::GetGraphDisplayInformation(Graph, DisplayInfo);
}
}
void UAnimationGraphSchema::AutoArrangeInterfaceGraph(UEdGraph& Graph)
{
// auto-arrange all nodes now
TArray<UAnimGraphNode_Root*> RootNodes;
Graph.GetNodesOfClass<UAnimGraphNode_Root>(RootNodes);
check(RootNodes.Num() == 1);
UAnimGraphNode_Root* Root = RootNodes[0];
TArray<UAnimGraphNode_LinkedInputPose*> LinkedInputPoseNodes;
Graph.GetNodesOfClass<UAnimGraphNode_LinkedInputPose>(LinkedInputPoseNodes);
FBox2D RootBounds(FVector2D(Root->NodePosX, Root->NodePosY), FVector2D(Root->NodePosX + 130, Root->NodePosY + 200));
double TotalHeight = 0.0;
double MaxWidth = 0.0;
const int32 HeightPerProperty = 30;
for(UAnimGraphNode_LinkedInputPose* Node : LinkedInputPoseNodes)
{
FBox2D LinkedInputPoseBounds(
FVector2D(Node->NodePosX, Node->NodePosY),
FVector2D(Node->NodePosX + 400.0, Node->NodePosY + 100.0 + (Node->GetNumInputs() * HeightPerProperty))
);
FVector2D BoundsSize = LinkedInputPoseBounds.GetSize();
TotalHeight += BoundsSize.Y + 10.0;
MaxWidth = FMath::Max(BoundsSize.X, MaxWidth);
}
double NodeOffset = RootBounds.GetCenter().Y - (TotalHeight * 0.5);
double NodePosX = RootBounds.Min.X - (MaxWidth + 100.0);
for(UAnimGraphNode_LinkedInputPose* Node : LinkedInputPoseNodes)
{
Node->NodePosX = static_cast<int32>(NodePosX);
Node->NodePosY = static_cast<int32>(NodeOffset);
FBox2D LinkedInputPoseBounds(
FVector2D(Node->NodePosX, Node->NodePosY),
FVector2D(Node->NodePosX + 400.0, Node->NodePosY + 100.0 + (Node->GetNumInputs() * HeightPerProperty))
);
NodeOffset += LinkedInputPoseBounds.GetSize().Y + 10.0;
}
}
void UAnimationGraphSchema::ConformAnimGraphToInterface(UBlueprint* InBlueprint, UEdGraph& InGraph, UFunction* InFunction)
{
if(const UAnimationGraphSchema* Schema = ExactCast<UAnimationGraphSchema>(InGraph.GetSchema()))
{
// Propagate group from metadata
TArray<UAnimGraphNode_Root*> RootNodes;
InGraph.GetNodesOfClass<UAnimGraphNode_Root>(RootNodes);
check(RootNodes.Num() == 1);
RootNodes[0]->Node.SetGroup(*FObjectEditorUtils::GetCategoryText(InFunction).ToString());
TArray<UAnimGraphNode_LinkedInputPose*> LinkedInputPoseNodes;
InGraph.GetNodesOfClass<UAnimGraphNode_LinkedInputPose>(LinkedInputPoseNodes);
for(UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode : LinkedInputPoseNodes)
{
// Sync pose names in case they have changed
LinkedInputPoseNode->ConformInputPoseName();
// Clean up any old linked input poses that no longer exist
if(!LinkedInputPoseNode->ValidateAgainstFunctionReference())
{
InGraph.RemoveNode(LinkedInputPoseNode);
}
}
UClass* InterfaceClass = CastChecked<UClass>(InFunction->GetOuter());
// Add any inputs that are not present in the graph (matching by pose index)
int32 CurrentPoseIndex = 0;
for (TFieldIterator<FProperty> PropIt(InFunction); PropIt && (PropIt->PropertyFlags & CPF_Parm); ++PropIt)
{
FProperty* Param = *PropIt;
const bool bIsFunctionInput = !Param->HasAnyPropertyFlags(CPF_OutParm) || Param->HasAnyPropertyFlags(CPF_ReferenceParm);
if (bIsFunctionInput)
{
FEdGraphPinType PinType;
if(Schema->ConvertPropertyToPinType(Param, PinType))
{
// Create linked input pose for each pose pin type
if(UAnimationGraphSchema::IsPosePin(PinType))
{
UAnimGraphNode_LinkedInputPose** MatchingNode = LinkedInputPoseNodes.FindByPredicate(
[CurrentPoseIndex](UAnimGraphNode_LinkedInputPose* InLinkedInputPoseNode)
{
return InLinkedInputPoseNode->InputPoseIndex == CurrentPoseIndex;
});
if(MatchingNode == nullptr)
{
const FName GraphName = InGraph.GetFName();
// Get the function GUID from the most up-to-date class
FGuid GraphGuid;
FBlueprintEditorUtils::GetFunctionGuidFromClassByFieldName(FBlueprintEditorUtils::GetMostUpToDateClass(InterfaceClass), GraphName, GraphGuid);
// not found, add this node
FGraphNodeCreator<UAnimGraphNode_LinkedInputPose> LinkedInputPoseNodeCreator(InGraph);
UAnimGraphNode_LinkedInputPose* LinkedInputPoseNode = LinkedInputPoseNodeCreator.CreateNode();
LinkedInputPoseNode->FunctionReference.SetExternalMember(GraphName, InterfaceClass, GraphGuid);
LinkedInputPoseNode->Node.Name = Param->GetFName();
LinkedInputPoseNode->InputPoseIndex = CurrentPoseIndex;
LinkedInputPoseNode->ReconstructNode();
SetNodeMetaData(LinkedInputPoseNode, FNodeMetadata::DefaultGraphNode);
LinkedInputPoseNodeCreator.Finalize();
FVector2D NewPosition = GetPositionForNewLinkedInputPoseNode(InGraph);
LinkedInputPoseNode->NodePosX = static_cast<int32>(NewPosition.X);
LinkedInputPoseNode->NodePosY = static_cast<int32>(NewPosition.Y);
}
CurrentPoseIndex++;
}
}
}
}
}
}
void UAnimationGraphSchema::ConformAnimLayersByGuid(const UAnimBlueprint* InAnimBlueprint, const FBPInterfaceDescription& CurrentInterfaceDesc)
{
const UBlueprint* InterfaceBlueprint = CastChecked<UBlueprint>(CurrentInterfaceDesc.Interface->ClassGeneratedBy);
TArray<UEdGraph*> InterfaceGraphs;
InterfaceBlueprint->GetAllGraphs(InterfaceGraphs);
TArray<UEdGraph*> Graphs;
InAnimBlueprint->GetAllGraphs(Graphs);
for (UEdGraph* Graph : Graphs)
{
TArray<UAnimGraphNode_LinkedAnimLayer*> LayerNodes;
Graph->GetNodesOfClass<UAnimGraphNode_LinkedAnimLayer>(LayerNodes);
for (UAnimGraphNode_LinkedAnimLayer* LayerNode : LayerNodes)
{
LayerNode->UpdateGuidForLayer();
if (LayerNode->InterfaceGuid.IsValid())
{
for (UEdGraph* InterfaceGraph : InterfaceGraphs)
{
// Check to see if GUID matches but name does not and update if so
if (InterfaceGraph->GraphGuid == LayerNode->InterfaceGuid && InterfaceGraph->GetFName() != LayerNode->GetLayerName())
{
LayerNode->SetLayerName(InterfaceGraph->GetFName());
}
}
}
}
}
}
FVector2D UAnimationGraphSchema::GetPositionForNewLinkedInputPoseNode(UEdGraph& InGraph)
{
TArray<UAnimGraphNode_LinkedInputPose*> LinkedInputPoseNodes;
InGraph.GetNodesOfClass<UAnimGraphNode_LinkedInputPose>(LinkedInputPoseNodes);
if(LinkedInputPoseNodes.Num() == 0)
{
TArray<UAnimGraphNode_Base*> AllNodes;
InGraph.GetNodesOfClass<UAnimGraphNode_Base>(AllNodes);
// No nodes, so insert to the top-left of all existing nodes.
FBox2D AllNodesBounds(ForceInit);
for(UAnimGraphNode_Base* Node : AllNodes)
{
FBox2D NodeBounds(
FVector2D(Node->NodePosX, Node->NodePosY),
FVector2D(Node->NodePosX + 400, Node->NodePosY + 100)
);
AllNodesBounds += NodeBounds;
}
return FVector2D(AllNodesBounds.Min.X - 300.0f, AllNodesBounds.Min.Y);
}
else
{
const int32 HeightPerProperty = 30;
// Some existing linked input poses. Insert below the bottom-most one.
UAnimGraphNode_LinkedInputPose* Node = LinkedInputPoseNodes[0];
FBox2D BottomMostLinkedInputPoseBounds(
FVector2D(Node->NodePosX, Node->NodePosY),
FVector2D(Node->NodePosX + 400, Node->NodePosY + 100 + (Node->GetNumInputs() * HeightPerProperty))
);
for(int32 LinkedInputPoseIndex = 1; LinkedInputPoseIndex < LinkedInputPoseNodes.Num(); ++LinkedInputPoseIndex)
{
Node = LinkedInputPoseNodes[LinkedInputPoseIndex];
FBox2D LinkedInputPoseBounds(
FVector2D(Node->NodePosX, Node->NodePosY),
FVector2D(Node->NodePosX + 400, Node->NodePosY + 100 + (Node->GetNumInputs() * HeightPerProperty))
);
if(LinkedInputPoseBounds.Min.Y > BottomMostLinkedInputPoseBounds.Min.Y)
{
BottomMostLinkedInputPoseBounds = LinkedInputPoseBounds;
}
}
return FVector2D(BottomMostLinkedInputPoseBounds.Min.X, BottomMostLinkedInputPoseBounds.Max.Y + 10.0f);
}
}
#undef LOCTEXT_NAMESPACE