// Copyright Epic Games, Inc. All Rights Reserved. #include "BlueprintComponentNodeSpawner.h" #include "BlueprintEditorModule.h" #include "BlueprintNodeTemplateCache.h" #include "ComponentAssetBroker.h" #include "ComponentTypeRegistry.h" #include "Components/ActorComponent.h" #include "Containers/Array.h" #include "Containers/Set.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphSchema_K2.h" #include "Engine/Blueprint.h" #include "Engine/BlueprintGeneratedClass.h" #include "Engine/MemberReference.h" #include "GameFramework/Actor.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "K2Node_AddComponent.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Kismet2/KismetEditorUtilities.h" #include "Misc/AssertionMacros.h" #include "Misc/PackageName.h" #include "Styling/SlateIconFinder.h" #include "Templates/Casts.h" #include "Templates/SharedPointer.h" #include "Textures/SlateIcon.h" #include "UObject/Class.h" #include "UObject/NameTypes.h" #include "UObject/Object.h" #include "UObject/ObjectPtr.h" #include "UObject/Package.h" #include "UObject/UnrealType.h" #include "UObject/WeakObjectPtr.h" #include "UObject/WeakObjectPtrTemplates.h" #define LOCTEXT_NAMESPACE "BlueprintComponenetNodeSpawner" /******************************************************************************* * Static UBlueprintComponentNodeSpawner Helpers ******************************************************************************/ namespace BlueprintComponentNodeSpawnerImpl { //------------------------------------------------------------------------------ static FText GetMenuCategoryFormat() { return LOCTEXT("ComponentCategory", "Add Component|{0}"); } //------------------------------------------------------------------------------ static FText GetDefaultMenuCategory(const TSubclassOf ComponentClass) { TArray ClassGroupNames; ComponentClass->GetClassGroupNames(ClassGroupNames); static const FText DefaultClassGroup(LOCTEXT("DefaultClassGroup", "Common")); // 'Common' takes priority over other class groups if (ClassGroupNames.Contains(DefaultClassGroup.ToString()) || (ClassGroupNames.Num() == 0)) { return DefaultClassGroup; } return FText::FromString(ClassGroupNames[0]); } } /******************************************************************************* * UBlueprintComponentNodeSpawner ******************************************************************************/ //------------------------------------------------------------------------------ UBlueprintComponentNodeSpawner* UBlueprintComponentNodeSpawner::Create(const FComponentTypeEntry& Entry) { using namespace BlueprintComponentNodeSpawnerImpl; UClass* ComponentClass = Entry.ComponentClass; if (ComponentClass == nullptr) { // unloaded class, must be blueprint created. Create an entry. We'll load the class when we spawn the node: UBlueprintComponentNodeSpawner* NodeSpawner = NewObject(GetTransientPackage()); NodeSpawner->ComponentClass = nullptr; NodeSpawner->NodeClass = UK2Node_AddComponent::StaticClass(); NodeSpawner->ComponentName = Entry.ComponentName; NodeSpawner->ComponentAssetName = Entry.ComponentAssetName; FBlueprintActionUiSpec& MenuSignature = NodeSpawner->DefaultMenuSignature; FText const ComponentTypeName = FText::FromString(Entry.ComponentName); MenuSignature.MenuName = FText::Format(LOCTEXT("AddComponentMenuName", "Add {0}"), ComponentTypeName); // Note: Non-native (i.e. BP) component types are automatically assigned to the "Custom" group name. // => @see FBlueprintEditorUtils::RecreateClassMetaData() MenuSignature.Category = FText::Format(GetMenuCategoryFormat(), LOCTEXT("BlueprintComponentCategory", "Custom")); MenuSignature.Tooltip = FText::Format(LOCTEXT("AddComponentTooltip", "Spawn a {0}"), ComponentTypeName); // add at least one character, so that PrimeDefaultUiSpec() doesn't // attempt to query the template node if (MenuSignature.Keywords.IsEmpty()) { MenuSignature.Keywords = FText::FromString(TEXT(" ")); } // Note: Currently using whatever styling is in place for UActorComponent. Note that the actual // class when loaded could be a USceneComponent derivative, in which case it might end up having // an alternate styling. For now just going with this as the placeholder to match the basic type. MenuSignature.Icon = FSlateIconFinder::FindIconForClass(UActorComponent::StaticClass()); MenuSignature.DocLink = TEXT("Shared/GraphNodes/Blueprint/UK2Node_AddComponent"); MenuSignature.DocExcerptTag = TEXT("AddComponent"); return NodeSpawner; } if (ComponentClass->ClassWithin && ComponentClass->ClassWithin != UObject::StaticClass()) { // we can't support 'Within' markup on components at this time (core needs to be aware of non-CDO archetypes // that have within markup, and BP system needs to property use RF_ArchetypeObject on template objects) return nullptr; } UClass* const AuthoritativeClass = ComponentClass->GetAuthoritativeClass(); UBlueprintComponentNodeSpawner* NodeSpawner = NewObject(GetTransientPackage()); NodeSpawner->ComponentClass = AuthoritativeClass; NodeSpawner->NodeClass = UK2Node_AddComponent::StaticClass(); FBlueprintActionUiSpec& MenuSignature = NodeSpawner->DefaultMenuSignature; FText const ComponentTypeName = AuthoritativeClass->GetDisplayNameText(); MenuSignature.MenuName = FText::Format(LOCTEXT("AddComponentMenuName", "Add {0}"), ComponentTypeName); MenuSignature.Category = FText::Format(GetMenuCategoryFormat(), GetDefaultMenuCategory(AuthoritativeClass)); MenuSignature.Tooltip = FText::Format(LOCTEXT("AddComponentTooltip", "Spawn a {0}"), ComponentTypeName); MenuSignature.Keywords = AuthoritativeClass->GetMetaDataText(*FBlueprintMetadata::MD_FunctionKeywords.ToString(), TEXT("UObjectKeywords"), AuthoritativeClass->GetFullGroupName(false)); // add at least one character, so that PrimeDefaultUiSpec() doesn't // attempt to query the template node if (MenuSignature.Keywords.IsEmpty()) { MenuSignature.Keywords = FText::FromString(TEXT(" ")); } MenuSignature.Icon = FSlateIconFinder::FindIconForClass(AuthoritativeClass); MenuSignature.DocLink = TEXT("Shared/GraphNodes/Blueprint/UK2Node_AddComponent"); MenuSignature.DocExcerptTag = AuthoritativeClass->GetName(); return NodeSpawner; } //------------------------------------------------------------------------------ UBlueprintComponentNodeSpawner::UBlueprintComponentNodeSpawner(FObjectInitializer const& ObjectInitializer) : Super(ObjectInitializer) { } //------------------------------------------------------------------------------ FBlueprintNodeSignature UBlueprintComponentNodeSpawner::GetSpawnerSignature() const { FBlueprintNodeSignature SpawnerSignature(NodeClass); SpawnerSignature.AddSubObject(ComponentClass.Get()); return SpawnerSignature; } //------------------------------------------------------------------------------ // Evolved from a combination of FK2ActionMenuBuilder::CreateAddComponentAction() // and FEdGraphSchemaAction_K2AddComponent::PerformAction(). UEdGraphNode* UBlueprintComponentNodeSpawner::Invoke(UEdGraph* ParentGraph, FBindingSet const& Bindings, FVector2D const Location) const { UClass* ComponentType = ComponentClass; auto PostSpawnLambda = [ComponentType](UEdGraphNode* NewNode, bool bIsTemplateNode, FCustomizeNodeDelegate UserDelegate) { UK2Node_AddComponent* AddCompNode = CastChecked(NewNode); UBlueprint* Blueprint = AddCompNode->GetBlueprint(); UFunction* AddComponentFunc = FindFieldChecked(AActor::StaticClass(), UK2Node_AddComponent::GetAddComponentFunctionName()); AddCompNode->FunctionReference.SetFromField(AddComponentFunc, !bIsTemplateNode && FBlueprintEditorUtils::IsActorBased(Blueprint)); AddCompNode->TemplateType = ComponentType; UserDelegate.ExecuteIfBound(NewNode, bIsTemplateNode); }; FCustomizeNodeDelegate PostSpawnDelegate = FCustomizeNodeDelegate::CreateLambda(PostSpawnLambda, CustomizeNodeDelegate); // let SpawnNode() allocate default pins (so we can modify them) UK2Node_AddComponent* NewNode = Super::SpawnNode(NodeClass, ParentGraph, FBindingSet(), Location, PostSpawnDelegate); if (NewNode->Pins.Num() == 0) { NewNode->AllocateDefaultPins(); } // set the return type to be the type of the template UEdGraphPin* ReturnPin = NewNode->GetReturnValuePin(); if (ReturnPin != nullptr) { if (ComponentClass != nullptr) { ReturnPin->PinType.PinSubCategoryObject = ComponentType; } else { ReturnPin->PinType.PinSubCategoryObject = UActorComponent::StaticClass(); } } bool const bIsTemplateNode = FBlueprintNodeTemplateCache::IsTemplateOuter(ParentGraph); if (!bIsTemplateNode) { TSubclassOf Class = ComponentClass; if (Class == nullptr) { const ELoadFlags LoadFlags = LOAD_None; UObject* LoadedObject = LoadObject(/*Outer =*/ nullptr, *ComponentAssetName, /*Filename =*/ nullptr, LoadFlags); if (LoadedObject == nullptr) { return nullptr; } // Note: The asset name may refer to either the generated class or the outer BP asset. if (const UBlueprint* LoadedObjectAsBlueprint = Cast(LoadedObject)) { Class = TSubclassOf(LoadedObjectAsBlueprint->GeneratedClass); } else { Class = TSubclassOf(Cast(LoadedObject)); } if (Class == nullptr) { return nullptr; } // Since the node has already been spawned, we need to update its template type. // Note that the return pin's type will be updated when we reconstruct the node below. NewNode->TemplateType = Class; } UActorComponent* ComponentTemplate = nullptr; if (FKismetEditorUtilities::IsClassABlueprintSpawnableComponent(Class)) { UBlueprint* Blueprint = NewNode->GetBlueprint(); FName DesiredComponentName = NewNode->MakeNewComponentTemplateName(Blueprint->GeneratedClass, Class); ComponentTemplate = NewObject(Blueprint->GeneratedClass, Class, DesiredComponentName, RF_ArchetypeObject | RF_Public | RF_Transactional); Blueprint->ComponentTemplates.Add(ComponentTemplate); } // set the name of the template as the default for the TemplateName param UEdGraphPin* TemplateNamePin = NewNode->GetTemplateNamePinChecked(); if (TemplateNamePin != nullptr) { TemplateNamePin->DefaultValue = ComponentTemplate ? ComponentTemplate->GetName() : TEXT(""); } NewNode->ReconstructNode(); } // apply bindings, after we've setup the template pin ApplyBindings(NewNode, Bindings); return NewNode; } //------------------------------------------------------------------------------ FBlueprintActionUiSpec UBlueprintComponentNodeSpawner::GetUiSpec(FBlueprintActionContext const& Context, FBindingSet const& Bindings) const { UEdGraph* TargetGraph = (Context.Graphs.Num() > 0) ? Context.Graphs[0] : nullptr; FBlueprintActionUiSpec MenuSignature = PrimeDefaultUiSpec(TargetGraph); if (Bindings.Num() > 0) { FText AssetName; { FBindingObject Binding = *Bindings.CreateConstIterator(); if (Binding.IsValid()) { AssetName = FText::FromName(Binding.GetFName()); } } FText const ComponentTypeName = FText::FromName(ComponentClass->GetFName()); MenuSignature.MenuName = FText::Format(LOCTEXT("AddBoundComponentMenuName", "Add {0} (as {1})"), AssetName, ComponentTypeName); MenuSignature.Tooltip = FText::Format(LOCTEXT("AddBoundComponentTooltip", "Spawn {0} using {1}"), ComponentTypeName, AssetName); } DynamicUiSignatureGetter.ExecuteIfBound(Context, Bindings, &MenuSignature); return MenuSignature; } //------------------------------------------------------------------------------ bool UBlueprintComponentNodeSpawner::IsBindingCompatible(FBindingObject BindingCandidate) const { bool bCanBindWith = false; if (BindingCandidate.IsUObject() && BindingCandidate.Get()->IsAsset()) { TArray< TSubclassOf > ComponentClasses = FComponentAssetBrokerage::GetComponentsForAsset(BindingCandidate.Get()); bCanBindWith = ComponentClasses.Contains(ComponentClass); } return bCanBindWith; } //------------------------------------------------------------------------------ bool UBlueprintComponentNodeSpawner::BindToNode(UEdGraphNode* Node, FBindingObject Binding) const { bool bSuccessfulBinding = false; UK2Node_AddComponent* AddCompNode = CastChecked(Node); UActorComponent* ComponentTemplate = AddCompNode->GetTemplateFromNode(); if (ComponentTemplate != nullptr) { check(!Binding.IsValid() || Binding.IsUObject()); // FProp bSuccessfulBinding = FComponentAssetBrokerage::AssignAssetToComponent(ComponentTemplate, Binding.Get()); AddCompNode->ReconstructNode(); } return bSuccessfulBinding; } //------------------------------------------------------------------------------ TSubclassOf UBlueprintComponentNodeSpawner::GetComponentClass() const { return ComponentClass; } //------------------------------------------------------------------------------ bool UBlueprintComponentNodeSpawner::IsTemplateNodeFilteredOut(const FBlueprintActionFilter& Filter) const { bool bIsFilteredOut = false; if (Filter.HasAnyFlags(FBlueprintActionFilter::BPFILTER_RejectNonImportedFields)) { TSharedPtr BlueprintEditor = Filter.Context.EditorPtr.Pin(); if (BlueprintEditor.IsValid()) { if (ComponentClass) { bIsFilteredOut = BlueprintEditor->IsNonImportedObject(ComponentClass); } else if(FPackageName::IsValidObjectPath(ComponentAssetName)) { bIsFilteredOut = BlueprintEditor->IsNonImportedObject(ComponentAssetName); } } } return bIsFilteredOut || UBlueprintNodeSpawner::IsTemplateNodeFilteredOut(Filter); } #undef LOCTEXT_NAMESPACE