// Copyright Epic Games, Inc. All Rights Reserved. #include "AnimGraphNode_BlendSpaceGraphBase.h" #include "GraphEditorActions.h" #include "Kismet2/CompilerResultsLog.h" #include "BlueprintNodeSpawner.h" #include "BlueprintActionDatabaseRegistrar.h" #include "Animation/AimOffsetBlendSpace.h" #include "Animation/AimOffsetBlendSpace1D.h" #include "Animation/AnimSync.h" #include "IAnimBlueprintCopyTermDefaultsContext.h" #include "IAnimBlueprintGeneratedClassCompiledData.h" #include "AnimationGraph.h" #include "AnimationGraphSchema.h" #include "Kismet2/BlueprintEditorUtils.h" #include "BlendSpaceGraph.h" #include "BlueprintEditorModule.h" #include "Kismet2/KismetEditorUtilities.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "EdGraphUtilities.h" #include "IAnimBlueprintCompilationContext.h" #include "AnimationBlendSpaceSampleGraph.h" #include "AnimBlueprintExtension.h" #include "AnimGraphNode_BlendSpaceSampleResult.h" #include "AnimGraphNode_SequencePlayer.h" #include "Kismet2/Kismet2NameValidators.h" #include "AnimNodes/AnimNode_BlendSpaceGraphBase.h" #include "BlueprintEditor.h" #include "Animation/AnimSequence.h" #include "AnimBlueprintExtension_BlendSpaceGraph.h" #include "AnimGraphNode_RandomPlayer.h" #include "BlueprintNodeTemplateCache.h" #include "Animation/AnimLayerInterface.h" #define LOCTEXT_NAMESPACE "UAnimGraphNode_BlendSpaceGraphBase" UAnimGraphNode_BlendSpaceGraphBase::UAnimGraphNode_BlendSpaceGraphBase() { bCanRenameNode = true; } FText UAnimGraphNode_BlendSpaceGraphBase::GetMenuCategory() const { return LOCTEXT("BlendSpaceCategory_Label", "Animation|Blend Spaces"); } FLinearColor UAnimGraphNode_BlendSpaceGraphBase::GetNodeTitleColor() const { return FLinearColor(0.8f, 0.8f, 0.8f); } FSlateIcon UAnimGraphNode_BlendSpaceGraphBase::GetIconAndTint(FLinearColor& OutColor) const { return FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.BlendSpace"); } FText UAnimGraphNode_BlendSpaceGraphBase::GetTooltipText() const { bool const bIsTemplateNode = GetGraph() == nullptr || FBlueprintNodeTemplateCache::IsTemplateOuter(GetGraph()); if(bIsTemplateNode) { return FText::GetEmpty(); } else { return GetNodeTitle(ENodeTitleType::ListView); } } UAnimGraphNode_Base* UAnimGraphNode_BlendSpaceGraphBase::ExpandGraphAndProcessNodes(UEdGraph* SourceGraph, UAnimGraphNode_Base* SourceRootNode, IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { // Clone the nodes from the source graph // Note that we outer this graph to the ConsolidatedEventGraph to allow ExpansionStep to // correctly retrieve the context for any expanded function calls (custom make/break structs etc.) UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(SourceGraph, InCompilationContext.GetConsolidatedEventGraph(), &InCompilationContext.GetMessageLog(), true); // Grab all the animation nodes and find the corresponding root node in the cloned set UAnimGraphNode_Base* TargetRootNode = nullptr; TArray AnimNodeList; for (auto NodeIt = ClonedGraph->Nodes.CreateIterator(); NodeIt; ++NodeIt) { UEdGraphNode* ClonedNode = *NodeIt; if (UAnimGraphNode_Base* TestNode = Cast(ClonedNode)) { AnimNodeList.Add(TestNode); //@TODO: There ought to be a better way to determine this if (InCompilationContext.GetMessageLog().FindSourceObject(TestNode) == InCompilationContext.GetMessageLog().FindSourceObject(SourceRootNode)) { TargetRootNode = TestNode; } } } check(TargetRootNode); // Run another expansion pass to catch the graph we just added (this is slightly wasteful) InCompilationContext.ExpansionStep(ClonedGraph, false); // Validate graph now we have expanded/pruned InCompilationContext.ValidateGraphIsWellFormed(ClonedGraph); // Move the cloned nodes into the consolidated event graph const bool bIsLoading = InCompilationContext.GetBlueprint()->bIsRegeneratingOnLoad || IsAsyncLoading(); const bool bIsCompiling = InCompilationContext.GetBlueprint()->bBeingCompiled; ClonedGraph->MoveNodesToAnotherGraph(InCompilationContext.GetConsolidatedEventGraph(), bIsLoading, bIsCompiling); // Process any animation nodes { TArray RootSet; RootSet.Add(TargetRootNode); InCompilationContext.PruneIsolatedAnimationNodes(RootSet, AnimNodeList); InCompilationContext.ProcessAnimationNodes(AnimNodeList); } // Returns the processed cloned version of SourceRootNode return TargetRootNode; } void UAnimGraphNode_BlendSpaceGraphBase::OnProcessDuringCompilation(IAnimBlueprintCompilationContext& InCompilationContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { auto ProcessGraph = [this, &OutCompiledData](UEdGraph* Graph, TArray AssetPlayerNodes, TArray AssetRandomPlayerPlayerNodes) { FString FunctionGraphName = Graph->GetName(); // Also make sure we do not process any empty stub graphs if (!FunctionGraphName.Contains(ANIM_FUNC_DECORATOR)) { if (Graph->Nodes.ContainsByPredicate([this, &OutCompiledData](UEdGraphNode* Node) { return Node && Node->NodeGuid == NodeGuid; })) { for (UAnimGraphNode_AssetPlayerBase* Node : AssetPlayerNodes) { if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid)) { FGraphAssetPlayerInformation& Info = OutCompiledData.GetGraphAssetPlayerInformation().FindOrAdd(FName(*FunctionGraphName)); Info.PlayerNodeIndices.AddUnique(*IndexPtr); } } for (UAnimGraphNode_RandomPlayer* Node : AssetRandomPlayerPlayerNodes) { if (int32* IndexPtr = OutCompiledData.GetAnimBlueprintDebugData().NodeGuidToIndexMap.Find(Node->NodeGuid)) { FGraphAssetPlayerInformation& Info = OutCompiledData.GetGraphAssetPlayerInformation().FindOrAdd(FName(*FunctionGraphName)); Info.PlayerNodeIndices.AddUnique(*IndexPtr); } } } } }; FStructProperty* NodeProperty = GetFNodeProperty(); FArrayProperty* PoseLinksProperty = CastFieldChecked(NodeProperty->Struct->FindPropertyByName(GET_MEMBER_NAME_CHECKED(FAnimNode_BlendSpaceGraphBase, SamplePoseLinks))); // Resize pose links to match graphs FAnimNode_BlendSpaceGraphBase* AnimNode = NodeProperty->ContainerPtrToValuePtr(this); AnimNode->SamplePoseLinks.SetNum(Graphs.Num()); TArray AssetPlayerNodes; TArray AssetRandomPlayerPlayerNodes; for(int32 PoseIndex = 0; PoseIndex < Graphs.Num(); ++PoseIndex) { UAnimationBlendSpaceSampleGraph* SampleGraph = CastChecked(Graphs[PoseIndex]); UAnimGraphNode_Base* RootNode = ExpandGraphAndProcessNodes(SampleGraph, SampleGraph->ResultNode, InCompilationContext, OutCompiledData); InCompilationContext.AddPoseLinkMappingRecord(FPoseLinkMappingRecord::MakeFromArrayEntry(this, RootNode, PoseLinksProperty, PoseIndex)); SampleGraph->GetNodesOfClass(AssetPlayerNodes); SampleGraph->GetNodesOfClass(AssetRandomPlayerPlayerNodes); } // Append player nodes to the owning graph's list UBlueprint* Blueprint = GetBlueprint(); for (UEdGraph* FunctionGraph : Blueprint->FunctionGraphs) { ProcessGraph(FunctionGraph, AssetPlayerNodes, AssetRandomPlayerPlayerNodes); } // Now do the same for implemented layer interfaces for (FBPInterfaceDescription& InterfaceDesc : Blueprint->ImplementedInterfaces) { // Only process Anim Layer interfaces if (InterfaceDesc.Interface->IsChildOf()) { for (UEdGraph* Graph : InterfaceDesc.Graphs) { ProcessGraph(Graph, AssetPlayerNodes, AssetRandomPlayerPlayerNodes); } } } } void UAnimGraphNode_BlendSpaceGraphBase::OnCopyTermDefaultsToDefaultObject(IAnimBlueprintCopyTermDefaultsContext& InCompilationContext, IAnimBlueprintNodeCopyTermDefaultsContext& InPerNodeContext, IAnimBlueprintGeneratedClassCompiledData& OutCompiledData) { FAnimNode_BlendSpaceGraphBase* DestinationNode = reinterpret_cast(InPerNodeContext.GetDestinationPtr()); UAnimBlueprintExtension_BlendSpaceGraph* Extension = UAnimBlueprintExtension::GetExtension(GetAnimBlueprint()); DestinationNode->BlendSpace = Extension->AddBlendSpace(BlendSpace); } void UAnimGraphNode_BlendSpaceGraphBase::SetupFromAsset(const FAssetData& InAssetData, bool bInIsTemplateNode) { if(InAssetData.IsValid()) { InAssetData.GetTagValue("Skeleton", SkeletonName); if(SkeletonName == TEXT("None")) { SkeletonName.Empty(); } if(!bInIsTemplateNode) { UBlendSpace* Asset = CastChecked(InAssetData.GetAsset()); UAnimBlueprint* AnimBlueprint = GetAnimBlueprint(); const UAnimationGraphSchema* AnimationGraphSchema = GetDefault(); BlendSpaceGraph = CastChecked(FBlueprintEditorUtils::CreateNewGraph(this, Asset->GetFName(), UBlendSpaceGraph::StaticClass(), UEdGraphSchema::StaticClass())); BlendSpaceGraph->BlendSpace = BlendSpace = DuplicateObject(Asset, BlendSpaceGraph); BlendSpaceGraph->BlendSpace->ClearFlags(RF_Public | RF_Standalone | RF_Transient); BlendSpaceGraph->BlendSpace->SetFlags(RF_Transactional); BlendSpaceGraph->BlendSpace->SetSkeleton(nullptr); BlendSpaceGraph->BlendSpace->EmptyMetaData(); BlendSpaceGraph->BlendSpace->RemoveUserDataOfClass(UAssetUserData::StaticClass()); BlendSpaceGraph->bAllowDeletion = false; BlendSpaceGraph->bAllowRenaming = true; // Add the new graph as a child of our parent graph UEdGraph* ParentGraph = GetGraph(); if(ParentGraph->SubGraphs.Find(BlendSpaceGraph) == INDEX_NONE) { ParentGraph->Modify(); ParentGraph->SubGraphs.Add(BlendSpaceGraph); } for(int32 SampleIndex = 0; SampleIndex < BlendSpace->SampleData.Num(); ++SampleIndex) { FBlendSample& Sample = BlendSpace->SampleData[SampleIndex]; FName SampleName = Sample.Animation != nullptr ? Sample.Animation->GetFName() : FName("Sample", SampleIndex); UAnimationBlendSpaceSampleGraph* SampleGraph = AddGraph(SampleName, Sample.Animation); // Clear the animation now we have created the point Sample.Animation = nullptr; } } } } void UAnimGraphNode_BlendSpaceGraphBase::SetupFromClass(TSubclassOf InBlendSpaceClass, bool bInIsTemplateNode) { if(bInIsTemplateNode) { BlendSpaceClass = InBlendSpaceClass; } else { BlendSpaceGraph = CastChecked(FBlueprintEditorUtils::CreateNewGraph(this, InBlendSpaceClass.Get()->GetFName(), UBlendSpaceGraph::StaticClass(), UEdGraphSchema::StaticClass())); BlendSpaceGraph->BlendSpace = BlendSpace = NewObject(BlendSpaceGraph, InBlendSpaceClass.Get()); BlendSpaceGraph->BlendSpace->ClearFlags(RF_Public | RF_Standalone | RF_Transient); BlendSpaceGraph->BlendSpace->SetFlags(RF_Transactional); BlendSpaceGraph->bAllowDeletion = false; BlendSpaceGraph->bAllowRenaming = true; // Add the new graph as a child of our parent graph UEdGraph* ParentGraph = GetGraph(); if(ParentGraph->SubGraphs.Find(BlendSpaceGraph) == INDEX_NONE) { ParentGraph->Modify(); ParentGraph->SubGraphs.Add(BlendSpaceGraph); } } } UObject* UAnimGraphNode_BlendSpaceGraphBase::GetJumpTargetForDoubleClick() const { return BlendSpaceGraph; } void UAnimGraphNode_BlendSpaceGraphBase::JumpToDefinition() const { TSharedPtr BlueprintEditor = FKismetEditorUtilities::GetIBlueprintEditorForObject(BlendSpace, true); if(BlueprintEditor.IsValid()) { BlueprintEditor->JumpToHyperlink(BlendSpaceGraph, false); } } void UAnimGraphNode_BlendSpaceGraphBase::CustomizeDetails(IDetailLayoutBuilder& InDetailBuilder) { if (BlendSpace) { IDetailCategoryBuilder& BlendSpaceCategory = InDetailBuilder.EditCategory("Blend Space"); IDetailPropertyRow* BlendSpaceRow = BlendSpaceCategory.AddExternalObjects( { BlendSpace } ); BlendSpaceRow->ShouldAutoExpand(true); TSharedRef XHandle = InDetailBuilder.GetProperty(TEXT("Node.X"), GetClass()); XHandle->SetPropertyDisplayName(FText::FromString(BlendSpace->GetBlendParameter(0).DisplayName)); TSharedRef YHandle = InDetailBuilder.GetProperty(TEXT("Node.Y"), GetClass()); if (BlendSpace->IsA()) { InDetailBuilder.HideProperty(YHandle); } else { YHandle->SetPropertyDisplayName(FText::FromString(BlendSpace->GetBlendParameter(1).DisplayName)); } } } TArray UAnimGraphNode_BlendSpaceGraphBase::GetSubGraphs() const { return TArray( { BlendSpaceGraph } ); } void UAnimGraphNode_BlendSpaceGraphBase::DestroyNode() { UBlueprint* Blueprint = GetBlueprint(); for (UEdGraph* SampleGraph : Graphs) { SampleGraph->Modify(); FBlueprintEditorUtils::RemoveGraph(Blueprint, SampleGraph); } UEdGraph* GraphToRemove = BlendSpaceGraph; BlendSpaceGraph = nullptr; Graphs.Empty(); Super::DestroyNode(); if (GraphToRemove) { GraphToRemove->Modify(); FBlueprintEditorUtils::RemoveGraph(Blueprint, GraphToRemove, EGraphRemoveFlags::Recompile); } } void UAnimGraphNode_BlendSpaceGraphBase::PreloadRequiredAssets() { PreloadObject(BlendSpace); Super::PreloadRequiredAssets(); } TSharedPtr UAnimGraphNode_BlendSpaceGraphBase::MakeNameValidator() const { class FNameValidator : public FStringSetNameValidator { public: FNameValidator(const UAnimGraphNode_BlendSpaceGraphBase* InBlendSpaceGraphNode) : FStringSetNameValidator(FString()) { TArray Nodes; UAnimationGraph* AnimGraph = CastChecked(InBlendSpaceGraphNode->GetOuter()); AnimGraph->GetNodesOfClass(Nodes); for (UAnimGraphNode_BlendSpaceGraphBase* Node : Nodes) { if (Node != InBlendSpaceGraphNode) { Names.Add(Node->GetBlendSpaceGraphName()); } } } }; return MakeShared(this); } void UAnimGraphNode_BlendSpaceGraphBase::OnRenameNode(const FString& NewName) { FBlueprintEditorUtils::RenameGraph(BlendSpaceGraph, NewName); } FString UAnimGraphNode_BlendSpaceGraphBase::GetBlendSpaceGraphName() const { return (BlendSpaceGraph != nullptr) ? *(BlendSpaceGraph->GetName()) : TEXT("(null)"); } FString UAnimGraphNode_BlendSpaceGraphBase::GetBlendSpaceName() const { return (BlendSpace != nullptr) ? *(BlendSpace->GetName()) : TEXT("(null)"); } void UAnimGraphNode_BlendSpaceGraphBase::PostPasteNode() { Super::PostPasteNode(); if(BlendSpaceGraph != nullptr) { // Add the new graph as a child of our parent graph UEdGraph* ParentGraph = GetGraph(); if(ParentGraph->SubGraphs.Find(BlendSpaceGraph) == INDEX_NONE) { ParentGraph->SubGraphs.Add(BlendSpaceGraph); } // Refresh sample graphs for (UEdGraph* SampleGraph : Graphs) { for(UEdGraphNode* GraphNode : SampleGraph->Nodes) { GraphNode->CreateNewGuid(); GraphNode->PostPasteNode(); GraphNode->ReconstructNode(); } } // Find an interesting name TSharedPtr NameValidator = FNameValidatorFactory::MakeValidator(this); FBlueprintEditorUtils::RenameGraphWithSuggestion(BlendSpaceGraph, NameValidator, BlendSpaceGraph->GetName()); // Restore transactional flag that is lost during copy/paste process BlendSpaceGraph->SetFlags(RF_Transactional); } } void UAnimGraphNode_BlendSpaceGraphBase::PostPlacedNewNode() { Super::PostPlacedNewNode(); // Create a new graph & blendspace if we havent been set up already if(BlendSpaceGraph == nullptr) { check(BlendSpace == nullptr); BlendSpaceGraph = CastChecked(FBlueprintEditorUtils::CreateNewGraph(this, NAME_None, UBlendSpaceGraph::StaticClass(), UEdGraphSchema::StaticClass())); BlendSpaceGraph->BlendSpace = BlendSpace = NewObject(); // Find an interesting name TSharedPtr NameValidator = FNameValidatorFactory::MakeValidator(this); FBlueprintEditorUtils::RenameGraphWithSuggestion(BlendSpaceGraph, NameValidator, TEXT("New Blend Space")); // Add the new graph as a child of our parent graph UEdGraph* ParentGraph = GetGraph(); if(ParentGraph->SubGraphs.Find(BlendSpaceGraph) == INDEX_NONE) { ParentGraph->Modify(); ParentGraph->SubGraphs.Add(BlendSpaceGraph); } } } void UAnimGraphNode_BlendSpaceGraphBase::CustomizePinData(UEdGraphPin* Pin, FName SourcePropertyName, int32 ArrayIndex) const { if (BlendSpace != nullptr) { if (SourcePropertyName == TEXT("X")) { Pin->PinFriendlyName = FText::FromString(BlendSpace->GetBlendParameter(0).DisplayName); } else if (SourcePropertyName == TEXT("Y")) { Pin->PinFriendlyName = FText::FromString(BlendSpace->GetBlendParameter(1).DisplayName); Pin->bHidden = BlendSpace->IsA() ? 1 : 0; } else if (SourcePropertyName == TEXT("Z")) { Pin->PinFriendlyName = FText::FromString(BlendSpace->GetBlendParameter(2).DisplayName); } } } void UAnimGraphNode_BlendSpaceGraphBase::PostProcessPinName(const UEdGraphPin* Pin, FString& DisplayName) const { if(Pin->Direction == EGPD_Input) { if(BlendSpace != nullptr) { if(Pin->PinName == TEXT("X")) { DisplayName = BlendSpace->GetBlendParameter(0).DisplayName; } else if(Pin->PinName == TEXT("Y")) { DisplayName = BlendSpace->GetBlendParameter(1).DisplayName; } else if(Pin->PinName == TEXT("Z")) { DisplayName = BlendSpace->GetBlendParameter(2).DisplayName; } } } Super::PostProcessPinName(Pin, DisplayName); } int32 UAnimGraphNode_BlendSpaceGraphBase::GetSampleIndex(const UEdGraph* Graph) const { for (int32 Index = 0 ; Index != Graphs.Num() ; ++Index) { if (Graphs[Index] == Graph) { return Index; } } return INDEX_NONE; } UAnimationBlendSpaceSampleGraph* UAnimGraphNode_BlendSpaceGraphBase::AddGraph(FName InSampleName, UAnimSequence* InSequence) { UAnimationBlendSpaceSampleGraph* NewGraph = AddGraphInternal(InSampleName, InSequence); Graphs.Add(NewGraph); return NewGraph; } UAnimationBlendSpaceSampleGraph* UAnimGraphNode_BlendSpaceGraphBase::AddGraphInternal(FName InSampleName, UAnimSequence* InSequence) { const UAnimationGraphSchema* AnimationGraphSchema = GetDefault(); Modify(); const FName NewGraphName = FBlueprintEditorUtils::GenerateUniqueGraphName(BlendSpaceGraph, InSampleName.ToString()); UAnimationBlendSpaceSampleGraph* SampleGraph = CastChecked(FBlueprintEditorUtils::CreateNewGraph(BlendSpaceGraph, NewGraphName, UAnimationBlendSpaceSampleGraph::StaticClass(), UAnimationGraphSchema::StaticClass())); FGraphNodeCreator ResultNodeCreator(*SampleGraph); UAnimGraphNode_BlendSpaceSampleResult* ResultSinkNode = ResultNodeCreator.CreateNode(); ResultNodeCreator.Finalize(); AnimationGraphSchema->SetNodeMetaData(ResultSinkNode, FNodeMetadata::DefaultGraphNode); SampleGraph->ResultNode = ResultSinkNode; SampleGraph->bAllowDeletion = false; SampleGraph->bAllowRenaming = true; if(InSequence != nullptr) { // Attach an asset player if a valid animation is supplied FGraphNodeCreator SequencePlayerNodeCreator(*SampleGraph); UAnimGraphNode_SequencePlayer* SequencePlayer = SequencePlayerNodeCreator.CreateNode(); SequencePlayer->SetAnimationAsset(InSequence); SequencePlayer->Node.SetGroupMethod(EAnimSyncMethod::Graph); SequencePlayerNodeCreator.Finalize(); // Offset node in X SequencePlayer->NodePosX = SampleGraph->ResultNode->NodePosX - 400; UEdGraphPin* OutputPin = SequencePlayer->FindPinChecked(TEXT("Pose"), EGPD_Output); UEdGraphPin* InputPin = SampleGraph->ResultNode->FindPinChecked(TEXT("Result"), EGPD_Input); OutputPin->MakeLinkTo(InputPin); } BlendSpaceGraph->Modify(); BlendSpaceGraph->SubGraphs.Add(SampleGraph); return SampleGraph; } void UAnimGraphNode_BlendSpaceGraphBase::RemoveGraph(int32 InSampleIndex) { Modify(); check(Graphs.IsValidIndex(InSampleIndex)); UEdGraph* GraphToRemove = Graphs[InSampleIndex]; TSharedPtr BlueprintEditor = StaticCastSharedPtr(FKismetEditorUtilities::GetIBlueprintEditorForObject(GraphToRemove, false)); check(BlueprintEditor.IsValid()); // 'Asset' blendspace sample deletion uses a RemoveAtSwap, so we must mirror it here to maintain the same indices Graphs.RemoveAtSwap(InSampleIndex); FBlueprintEditorUtils::RemoveGraph(GetAnimBlueprint(), GraphToRemove); BlueprintEditor->CloseDocumentTab(GraphToRemove); } void UAnimGraphNode_BlendSpaceGraphBase::ReplaceGraph(int32 InSampleIndex, UAnimSequence* InSequence) { Modify(); check(Graphs.IsValidIndex(InSampleIndex)); UEdGraph* GraphToRemove = Graphs[InSampleIndex]; FName GraphName = GraphToRemove->GetFName(); TSharedPtr BlueprintEditor = StaticCastSharedPtr(FKismetEditorUtilities::GetIBlueprintEditorForObject(GraphToRemove, false)); check(BlueprintEditor.IsValid()); FBlueprintEditorUtils::RemoveGraph(GetAnimBlueprint(), GraphToRemove); BlueprintEditor->CloseDocumentTab(GraphToRemove); Graphs[InSampleIndex] = AddGraphInternal(GraphName, InSequence); } FName UAnimGraphNode_BlendSpaceGraphBase::GetSyncGroupName() const { FStructProperty* NodeProperty = GetFNodeProperty(); const FAnimNode_BlendSpaceGraphBase* AnimNode = NodeProperty->ContainerPtrToValuePtr(this); return AnimNode->GroupName; } void UAnimGraphNode_BlendSpaceGraphBase::SetSyncGroupName(FName InName) { FStructProperty* NodeProperty = GetFNodeProperty(); FAnimNode_BlendSpaceGraphBase* AnimNode = NodeProperty->ContainerPtrToValuePtr(this); AnimNode->GroupName = InName; } void UAnimGraphNode_BlendSpaceGraphBase::GetInputLinkAttributes(FNodeAttributeArray& OutAttributes) const { if(GetSyncGroupName() != NAME_None) { OutAttributes.Add(UE::Anim::FAnimSync::Attribute); } } void UAnimGraphNode_BlendSpaceGraphBase::GetRequiredExtensions(TArray>& OutExtensions) const { OutExtensions.Add(UAnimBlueprintExtension_BlendSpaceGraph::StaticClass()); } bool UAnimGraphNode_BlendSpaceGraphBase::IsActionFilteredOut(class FBlueprintActionFilter const& Filter) { bool bIsFilteredOut = false; FBlueprintActionContext const& FilterContext = Filter.Context; for (UBlueprint* Blueprint : FilterContext.Blueprints) { if (UAnimBlueprint* AnimBlueprint = Cast(Blueprint)) { if(!SkeletonName.IsEmpty()) { if(AnimBlueprint->TargetSkeleton == nullptr || !AnimBlueprint->TargetSkeleton->IsCompatibleForEditor(SkeletonName)) { bIsFilteredOut = true; break; } } } else { // Not an animation Blueprint, cannot use bIsFilteredOut = true; break; } } return bIsFilteredOut; } #undef LOCTEXT_NAMESPACE