TSharedRef<SDockTab> FMerge::GenerateMergeWidget(const UBlueprint& Object, TSharedRef<FBlueprintEditor> Editor)
{
	auto ActiveTabPtr = ActiveTab.Pin();
	if( ActiveTabPtr.IsValid() )
	{
		// just bring the tab to the foreground:
		auto CurrentTab = FGlobalTabmanager::Get()->InvokeTab(MergeToolTabId);
		check( CurrentTab == ActiveTabPtr );
		return ActiveTabPtr.ToSharedRef();
	}

	// merge the local asset with the depot, SCC provides us with the last common revision as
	// a basis for the merge:

	TSharedPtr<SWidget> Contents;

	if (!PendingMerge(Object))
	{
		// this should load up the merge-tool, with an asset picker, where they
		// can pick the asset/revisions to merge against
		Contents = GenerateMergeTabContents(Editor, nullptr, FRevisionInfo::InvalidRevision(), nullptr, FRevisionInfo::InvalidRevision(), &Object, FOnMergeResolved());
	}
	else
	{
		// @todo DO: this will probably need to be async.. pulling down some old versions of assets:
		const FString& PackageName = Object.GetOutermost()->GetName();
		const FString& AssetName = Object.GetName();

		FSourceControlStatePtr SourceControlState = FMergeToolUtils::GetSourceControlState(PackageName);
		if (!SourceControlState.IsValid())
		{
			DisplayErrorMessage(
				FText::Format(
					LOCTEXT("MergeFailedNoSourceControl", "Aborted Load of {0} from {1} because the source control state was invalidated")
					, FText::FromString(AssetName)
					, FText::FromString(PackageName)
				)
			);

			Contents = SNew(SHorizontalBox);
		}
		else
		{
			ISourceControlState const& SourceControlStateRef = *SourceControlState;

			FRevisionInfo CurrentRevInfo = FRevisionInfo::InvalidRevision();
			const UBlueprint* RemoteBlueprint = Cast< UBlueprint >(LoadHeadRev(PackageName, AssetName, SourceControlStateRef, CurrentRevInfo));
			FRevisionInfo BaseRevInfo = FRevisionInfo::InvalidRevision();
			const UBlueprint* BaseBlueprint = Cast< UBlueprint >(LoadBaseRev(PackageName, AssetName, SourceControlStateRef, BaseRevInfo));

			Contents = GenerateMergeTabContents(Editor, BaseBlueprint, BaseRevInfo, RemoteBlueprint, CurrentRevInfo, &Object, FOnMergeResolved());
		}
	}

	TSharedRef<SDockTab> Tab =  FGlobalTabmanager::Get()->InvokeTab(MergeToolTabId);
	Tab->SetContent(Contents.ToSharedRef());
	ActiveTab = Tab;
	return Tab;

}
Example #2
0
FText UK2Node_DynamicCast::GetNodeTitle(ENodeTitleType::Type TitleType) const
{
	if (TargetType == nullptr)
	{
		return NSLOCTEXT("K2Node_DynamicCast", "BadCastNode", "Bad cast node");
	}
	else if (CachedNodeTitle.IsOutOfDate())
	{
		// If casting to BP class, use BP name not class name (ie. remove the _C)
		FString TargetName;
		UBlueprint* CastToBP = UBlueprint::GetBlueprintFromClass(TargetType);
		if (CastToBP != NULL)
		{
			TargetName = CastToBP->GetName();
		}
		else
		{
			TargetName = TargetType->GetName();
		}

		FFormatNamedArguments Args;
		Args.Add(TEXT("TargetName"), FText::FromString(TargetName));

		// FText::Format() is slow, so we cache this to save on performance
		CachedNodeTitle = FText::Format(NSLOCTEXT("K2Node_DynamicCast", "CastTo", "Cast To {TargetName}"), Args);
	}
	return CachedNodeTitle;
}
void FAssetTypeActions_Blueprint::PerformAssetDiff(UObject* OldAsset, UObject* NewAsset, const FRevisionInfo& OldRevision, const FRevisionInfo& NewRevision) const
{
	UBlueprint* OldBlueprint = Cast<UBlueprint>(OldAsset);
	check(OldBlueprint != NULL);
	check(OldBlueprint->SkeletonGeneratedClass != NULL);

	UBlueprint* NewBlueprint = Cast<UBlueprint>(NewAsset);
	check(NewBlueprint != NULL);
	check(NewBlueprint->SkeletonGeneratedClass != NULL);

	// sometimes we're comparing different revisions of one single asset (other 
	// times we're comparing two completely separate assets altogether)
	bool bIsSingleAsset = (NewBlueprint->GetName() == OldBlueprint->GetName());

	FText WindowTitle = LOCTEXT("NamelessBlueprintDiff", "Blueprint Diff");
	// if we're diff'ing one asset against itself 
	if (bIsSingleAsset)
	{
		// identify the assumed single asset in the window's title
		WindowTitle = FText::Format(LOCTEXT("Blueprint Diff", "{0} - Blueprint Diff"), FText::FromString(NewBlueprint->GetName()));
	}

	const TSharedPtr<SWindow> Window = SNew(SWindow)
		.Title(WindowTitle)
		.ClientSize(FVector2D(1000,800));

	Window->SetContent(SNew(SBlueprintDiff)
					  .BlueprintOld(OldBlueprint)
					  .BlueprintNew(NewBlueprint)
					  .OldRevision(OldRevision)
					  .NewRevision(NewRevision)
					  .ShowAssetNames(!bIsSingleAsset)
					  .OpenInDefaults(const_cast<FAssetTypeActions_Blueprint*>(this), &FAssetTypeActions_Blueprint::OpenInDefaults) );

	// Make this window a child of the modal window if we've been spawned while one is active.
	TSharedPtr<SWindow> ActiveModal = FSlateApplication::Get().GetActiveModalWindow();
	if ( ActiveModal.IsValid() )
	{
		FSlateApplication::Get().AddWindowAsNativeChild( Window.ToSharedRef(), ActiveModal.ToSharedRef() );
	}
	else
	{
		FSlateApplication::Get().AddWindow( Window.ToSharedRef() );
	}
}
TSharedRef<SWidget> FEditorClassUtils::GetSourceLinkFormatted(const UClass* Class, const TWeakObjectPtr<UObject> ObjectWeakPtr, const FText& BlueprintFormat, const FText& CodeFormat)
{
	TSharedRef<SWidget> SourceHyperlink = SNew( SSpacer );
	UBlueprint* Blueprint = (Class ? Cast<UBlueprint>(Class->ClassGeneratedBy) : nullptr);

	if (Blueprint)
	{
		struct Local
		{
			static void OnEditBlueprintClicked( TWeakObjectPtr<UBlueprint> InBlueprint, TWeakObjectPtr<UObject> InAsset )
			{
				if (UBlueprint* BlueprintToEdit = InBlueprint.Get())
				{
					// Set the object being debugged if given an actor reference (if we don't do this before we edit the object the editor wont know we are debugging something)
					if (UObject* Asset = InAsset.Get())
					{
						check(Asset->GetClass()->ClassGeneratedBy == BlueprintToEdit);
						BlueprintToEdit->SetObjectBeingDebugged(Asset);
					}
					// Open the blueprint
					GEditor->EditObject( BlueprintToEdit );
				}
			}
		};

		TWeakObjectPtr<UBlueprint> BlueprintPtr = Blueprint;

		SourceHyperlink = SNew(SHyperlink)
			.Style(FEditorStyle::Get(), "Common.GotoBlueprintHyperlink")
			.OnNavigate_Static(&Local::OnEditBlueprintClicked, BlueprintPtr, ObjectWeakPtr)
			.Text(FText::Format(BlueprintFormat, FText::FromString(Blueprint->GetName())))
			.ToolTipText(NSLOCTEXT("SourceHyperlink", "EditBlueprint_ToolTip", "Click to edit the blueprint"));
	}
	else if( FSourceCodeNavigation::IsCompilerAvailable() )
	{
		FString ClassHeaderPath;
		if( FSourceCodeNavigation::FindClassHeaderPath( Class, ClassHeaderPath ) && IFileManager::Get().FileSize( *ClassHeaderPath ) != INDEX_NONE )
		{
			struct Local
			{
				static void OnEditCodeClicked( FString InClassHeaderPath )
				{
					FString AbsoluteHeaderPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*InClassHeaderPath);
					FSourceCodeNavigation::OpenSourceFile( AbsoluteHeaderPath );
				}
			};

			SourceHyperlink = SNew(SHyperlink)
				.Style(FEditorStyle::Get(), "Common.GotoNativeCodeHyperlink")
				.OnNavigate_Static(&Local::OnEditCodeClicked, ClassHeaderPath)
				.Text(FText::Format(CodeFormat, FText::FromString(FPaths::GetCleanFilename( *ClassHeaderPath ) ) ) )
				.ToolTipText(FText::Format(NSLOCTEXT("SourceHyperlink", "GoToCode_ToolTip", "Click to open this source file in {0}"), FSourceCodeNavigation::GetSuggestedSourceCodeIDE()));
		}
	}

	return SourceHyperlink;
}
//------------------------------------------------------------------------------
FText SBlueprintLibraryPalette::GetFilterClassName() const
{
	FText FilterDisplayString = LOCTEXT("All", "All");
	if (FilterClass != NULL)
	{
		UBlueprint* Blueprint = UBlueprint::GetBlueprintFromClass(FilterClass.Get());
		FilterDisplayString = FText::FromString((Blueprint != NULL) ? Blueprint->GetName() : FilterClass->GetName());
	}

	return FilterDisplayString;
}
/** Util to give better names for BP generated classes */
static FString GetClassDisplayName(const UObject* Object)
{
    const UClass* Class = Cast<UClass>(Object);
    if (Class != NULL)
    {
        UBlueprint* BP = UBlueprint::GetBlueprintFromClass(Class);
        if(BP != NULL)
        {
            return BP->GetName();
        }
    }
    return (Object) ? Object->GetName() : "None";
}
Example #7
0
void UK2Node_Variable::ValidateNodeDuringCompilation(class FCompilerResultsLog& MessageLog) const
{
	Super::ValidateNodeDuringCompilation(MessageLog);

	UProperty* VariableProperty = GetPropertyForVariable();

	// Local variables do not exist until much later in the compilation than this function can provide
	if (VariableProperty == NULL && !VariableReference.IsLocalScope())
	{
		if (!VariableReference.IsDeprecated())
		{
			FString OwnerName;

			UBlueprint* Blueprint = GetBlueprint();
			if (Blueprint != nullptr)
			{
				OwnerName = Blueprint->GetName();
				if (UClass* VarOwnerClass = VariableReference.GetMemberParentClass(Blueprint->GeneratedClass))
				{
					OwnerName = VarOwnerClass->GetName();
				}
			}
			FString const VarName = VariableReference.GetMemberName().ToString();

			FText const WarningFormat = LOCTEXT("VariableNotFound", "Could not find a variable named \"%s\" in '%s'.\nMake sure '%s' has been compiled for @@");
			MessageLog.Warning(*FString::Printf(*WarningFormat.ToString(), *VarName, *OwnerName, *OwnerName), this);
		}
		else
		{
			MessageLog.Warning(*FString::Printf(*LOCTEXT("VariableDeprecated", "Variable '%s' for @@ was deprecated.  Please update it.").ToString(), *VariableReference.GetMemberName().ToString()), this);
		}
	}

	if (VariableProperty && (VariableProperty->ArrayDim > 1))
	{
		MessageLog.Warning(*LOCTEXT("StaticArray_Warning", "@@ - the native property is a static array, which is not supported by blueprints").ToString(), this);
	}
}
void FAssetTypeActions_BlueprintGeneratedClass::ExecuteEditDefaults(TArray<TWeakObjectPtr<UBlueprintGeneratedClass>> Objects)
{
	TArray< UBlueprint* > Blueprints;

	FMessageLog EditorErrors("EditorErrors");
	EditorErrors.NewPage(LOCTEXT("ExecuteEditDefaultsNewLogPage", "Loading Blueprints"));

	for (auto ObjIt = Objects.CreateConstIterator(); ObjIt; ++ObjIt)
	{
		auto GeneratedClass = (*ObjIt).Get();
		UBlueprint* Object = GeneratedClass ? Cast<UBlueprint>(GeneratedClass->ClassGeneratedBy) : NULL;
		if ( Object )
		{
			// If the blueprint is valid, allow it to be added to the list, otherwise log the error.
			if (Object && Object->SkeletonGeneratedClass && Object->GeneratedClass )
			{
				Blueprints.Add(Object);
			}
			else
			{
				FFormatNamedArguments Arguments;
				Arguments.Add(TEXT("ObjectName"), FText::FromString(Object->GetName()));
				EditorErrors.Error(FText::Format(LOCTEXT("LoadBlueprint_FailedLog", "{ObjectName} could not be loaded because it derives from an invalid class.  Check to make sure the parent class for this blueprint hasn't been removed!"), Arguments ) );
			}
		}
	}

	if ( Blueprints.Num() > 0 )
	{
		FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked<FBlueprintEditorModule>( "Kismet" );
		TSharedRef< IBlueprintEditor > NewBlueprintEditor = BlueprintEditorModule.CreateBlueprintEditor(  EToolkitMode::Standalone, TSharedPtr<IToolkitHost>(), Blueprints );
	}

	// Report errors
	EditorErrors.Notify(LOCTEXT("OpenDefaults_Failed", "Opening Blueprint Defaults Failed!"));
}
/**
 * Fills the Blueprint menu with extra options
 */
void FillBlueprintOptions(FMenuBuilder& MenuBuilder, TArray<AActor*> SelectedActors)
{
	// Gather Blueprint classes for this actor
	TArray< FMenuBlueprintClass > BlueprintClasses;
	GatherBlueprintsForActors( SelectedActors, BlueprintClasses );

	MenuBuilder.BeginSection("ActorBlueprint", LOCTEXT("BlueprintsHeading", "Blueprints") );

	// Adds the "Create Blueprint..." menu option if valid.
	{
		int NumBlueprintableActors = 0;
		bool IsBlueprintBased = BlueprintClasses.Num() > 1;

		if(!BlueprintClasses.Num())
		{
			for(auto It(SelectedActors.CreateIterator());It;++It)
			{
				AActor* Actor = *It;
				if( FKismetEditorUtilities::CanCreateBlueprintOfClass(Actor->GetClass()))
				{
					NumBlueprintableActors++;
				}
			}
		}

		const bool bCanHarvestComponentsForBlueprint = (!IsBlueprintBased && (NumBlueprintableActors > 0));

		if(bCanHarvestComponentsForBlueprint)
		{
			AActor* ActorOverride = nullptr;
			FUIAction CreateBlueprintAction( FExecuteAction::CreateStatic( &FCreateBlueprintFromActorDialog::OpenDialog, true, ActorOverride ) );
			MenuBuilder.AddMenuEntry(LOCTEXT("CreateBlueprint", "Create Blueprint..."), LOCTEXT("CreateBlueprint_Tooltip", "Harvest Components from Selected Actors and create Blueprint"), FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.HarvestBlueprintFromActors"), CreateBlueprintAction);
		}
	}
	
	// Check to see if we have any classes with functions to display
	if( BlueprintClasses.Num() > 0 )
	{
		{
			UBlueprint* FirstBlueprint = Cast<UBlueprint>(BlueprintClasses[0].Blueprint.Get());

			// Determine if the selected objects that have blueprints are all of the same class, and if they are all up to date
			bool bAllAreSameType = true;
			bool bAreAnyNotUpToDate = false;
			for (int32 ClassIndex = 0; ClassIndex < BlueprintClasses.Num(); ++ClassIndex)
			{
				UBlueprint* CurrentBlueprint = Cast<UBlueprint>(BlueprintClasses[ClassIndex].Blueprint.Get());

				bAllAreSameType = bAllAreSameType && (CurrentBlueprint == FirstBlueprint);

				if (CurrentBlueprint != NULL)
				{
					bAreAnyNotUpToDate |= !CurrentBlueprint->IsUpToDate();
				}
			}

			// For a single selected class, we show a top level item (saves 2 clicks); otherwise we show the full hierarchy
			if (bAllAreSameType && (FirstBlueprint != NULL))
			{
				// Shortcut to edit the blueprint directly, saves two clicks
				FUIAction UIAction;
				UIAction.ExecuteAction.BindStatic(
					&EditKismetCodeFor,
					/*Blueprint=*/ TWeakObjectPtr<UBlueprint>(FirstBlueprint) );

				const FText Label = LOCTEXT("EditBlueprint", "Edit Blueprint");
				const FText Description = FText::Format( LOCTEXT("EditBlueprint_ToolTip", "Opens {0} in the Blueprint editor"), FText::FromString( FirstBlueprint->GetName() ) );

				MenuBuilder.AddMenuEntry( Label, Description, FSlateIcon(), UIAction );
			}
			else
			{
				// More than one type of blueprint is selected, so add a sub-menu for "Edit Kismet Code"
				MenuBuilder.AddSubMenu(
					LOCTEXT("EditBlueprintSubMenu", "Edit Blueprint"),
					LOCTEXT("EditBlueprintSubMenu_ToolTip", "Shows Blueprints that can be opened for editing"),
					FNewMenuDelegate::CreateStatic( &FillEditCodeMenu, BlueprintClasses ) );
			}

			// For any that aren't up to date, we offer a compile blueprints button
			if (bAreAnyNotUpToDate)
			{
				// Shortcut to edit the blueprint directly, saves two clicks
				FUIAction UIAction;
				UIAction.ExecuteAction.BindStatic(&RecompileOutOfDateKismetForSelection);

				const FText Label = LOCTEXT("CompileOutOfDateBPs", "Compile Out-of-Date Blueprints");
				const FText Description = LOCTEXT("CompileOutOfDateBPs_ToolTip", "Compiles out-of-date blueprints for selected actors");

				MenuBuilder.AddMenuEntry( Label, Description, FSlateIcon(), UIAction );
			}
		}
	}
	MenuBuilder.EndSection();
}
void FAssetTypeActions_BlueprintGeneratedClass::PerformAssetDiff(UObject* OldAsset, UObject* NewAsset, const FRevisionInfo& OldRevision, const FRevisionInfo& NewRevision) const
{
	UBlueprintGeneratedClass* OldClass = CastChecked<UBlueprintGeneratedClass>(OldAsset);
	UBlueprint* OldBlueprint = CastChecked<UBlueprint>(OldClass->ClassGeneratedBy);
	check(OldBlueprint->SkeletonGeneratedClass != NULL);

	UBlueprintGeneratedClass* NewClass = CastChecked<UBlueprintGeneratedClass>(NewAsset);
	UBlueprint* NewBlueprint = CastChecked<UBlueprint>(NewClass->ClassGeneratedBy);
	check(NewBlueprint->SkeletonGeneratedClass != NULL);

	const TSharedPtr<SWindow> Window = SNew(SWindow)
		.Title( FText::Format( LOCTEXT("Blueprint Diff", "{0} - Blueprint Diff"), FText::FromString( NewBlueprint->GetName() ) ) )
		.ClientSize(FVector2D(1000,800));

	Window->SetContent(SNew(SBlueprintDiff)
					  .BlueprintOld(OldBlueprint)
					  .BlueprintNew(NewBlueprint)
					  .OldRevision(OldRevision)
					  .NewRevision(NewRevision)
					  .OpenInDefaults(const_cast<FAssetTypeActions_BlueprintGeneratedClass*>(this), &FAssetTypeActions_BlueprintGeneratedClass::OpenInDefaults) );

	FSlateApplication::Get().AddWindow( Window.ToSharedRef(), true );
}
//------------------------------------------------------------------------------
UEdGraphNode* FBlueprintNodeTemplateCache::GetNodeTemplate(UBlueprintNodeSpawner const* NodeSpawner, UEdGraph* TargetGraph)
{
	using namespace BlueprintNodeTemplateCacheImpl;

	bool bIsOverMemCap = false;
	auto LogCacheFullMsg = [&bIsOverMemCap]()
	{
		if (!bIsOverMemCap)
		{
			static int32 LoggedCapSize = -1;
			int32 const CurrentCacheSize = GetCacheCapSize();
			// log only once for each cap size change
			if (LoggedCapSize != CurrentCacheSize)
			{
				UE_LOG(LogBlueprintNodeCache, Display, TEXT("The blueprint template-node cache is full. As a result, you may experience interactions which are slower than normal. To avoid this, increase the cache's cap in the blueprint editor prefences."));
				LoggedCapSize = CurrentCacheSize;
			}			
			bIsOverMemCap = true;
		}
	};
	

	UEdGraphNode* TemplateNode = nullptr;
	if (UEdGraphNode** FoundNode = NodeTemplateCache.Find(NodeSpawner))
	{
		TemplateNode = *FoundNode;
	}
	else if (NodeSpawner->NodeClass != nullptr)
	{
		UEdGraphNode* NodeCDO = NodeSpawner->NodeClass->GetDefaultObject<UEdGraphNode>();
		check(NodeCDO != nullptr);

		UBlueprint* TargetBlueprint = nullptr;
		TSubclassOf<UBlueprint> BlueprintClass;

		bool const bHasTargetGraph = (TargetGraph != nullptr);
		if (bHasTargetGraph)
		{
			// by the time we're asking for a prototype for this spawner, we should
			// be sure that it is compatible with the TargetGraph
			//check(IsCompatible(NodeCDO, TargetGraph));

			TargetBlueprint = FBlueprintEditorUtils::FindBlueprintForGraph(TargetGraph);
			check(TargetBlueprint != nullptr);
			BlueprintClass = TargetBlueprint->GetClass();

			// check used to help identify user interactable graphs (as opposed to 
			// intermediate/transient graphs).
			auto IsCompatibleUserGraph = [](UEdGraph* Graph)->bool
			{
				return !Graph->HasAnyFlags(RF_Transient);
			};

			TargetGraph = FindCompatibleGraph(TargetBlueprint, NodeCDO, IsCompatibleUserGraph);
			check(TargetGraph != nullptr);
		}		

		UBlueprint* CompatibleBlueprint = nullptr;
		UEdGraph*   CompatibleOuter     = nullptr;
		// find a compatible outer (don't want to have to create a new one if we don't have to)
		for (UBlueprint* Blueprint : TemplateOuters)
		{
			CompatibleOuter = FindCompatibleGraph(Blueprint, NodeCDO);
			if (CompatibleOuter != nullptr)
			{
				MarkGraphForTemplateUse(CompatibleOuter);
			}

			if (CompatibleOuter != nullptr)
			{
				CompatibleBlueprint = Blueprint;
				break;
			}
			else if ((BlueprintClass != nullptr) && Blueprint->GetClass()->IsChildOf(BlueprintClass))
			{
				CompatibleBlueprint = Blueprint;
			}
		}

		// reset ActiveMemFootprint, so calls to CacheBlueprintOuter()/CacheTemplateNode()
		// use the most up-to-date value (users could have since modified the
		// nodes, so they could have grown in size... like with AllocateDefaultPins)
		//
		// @TODO: GetEstimateCacheSize() is (most likely) inaccurate, seeing as
		//        external systems mutate template-nodes (such as calling
		//        AllocateDefaultPins), and this returns a size estimate from
		//        when the node was first spawned (it is too slow to recalculate
		//        the size of the object hierarchy here)
		ActiveMemFootprint = GetEstimateCacheSize();

		int32 const CacheCapSize = GetCacheCapSize();
		if (ActiveMemFootprint > CacheCapSize)
		{
			LogCacheFullMsg();
			// @TODO: evict nodes until we're back under the cap (in case the cap
			//        was changed at runtime, or external user modified node sizes)
		}

		// if a TargetGraph was supplied, and we couldn't find a suitable outer
		// for this template-node, then attempt to emulate that graph
		if (bHasTargetGraph)
		{
			if (CompatibleBlueprint == nullptr)
			{
				// if the cache is near full, attempt to predict if this  
				// impending cache will fail (if so, we don't want to waste the 
				// cycles on allocating a temp blueprint)
				if (!bIsOverMemCap && ((AverageBlueprintSize == 0) ||
					(ActiveMemFootprint + AverageBlueprintSize <= CacheCapSize)))
				{
					TSubclassOf<UBlueprintGeneratedClass> GeneratedClassType = UBlueprintGeneratedClass::StaticClass();
					if (TargetBlueprint->GeneratedClass != nullptr)
					{
						GeneratedClassType = TargetBlueprint->GeneratedClass->GetClass();
					}

					CompatibleBlueprint = MakeCompatibleBlueprint(BlueprintClass, TargetBlueprint->ParentClass, GeneratedClassType);
					if (!CacheBlueprintOuter(CompatibleBlueprint))
					{
						LogCacheFullMsg();
					}

					// this graph may come default with a compatible graph
					CompatibleOuter = FindCompatibleGraph(CompatibleBlueprint, NodeCDO);
					if (CompatibleOuter != nullptr)
					{
						MarkGraphForTemplateUse(CompatibleOuter);
					}
				}
				else
				{
 					CompatibleBlueprint = TargetBlueprint;
 					CompatibleOuter = FindCompatibleGraph(TargetBlueprint, NodeCDO, &BlueprintNodeTemplateCacheImpl::IsTemplateOuter);

					LogCacheFullMsg();
				}
			}

			if (CompatibleOuter == nullptr)
			{
				CompatibleOuter = AddGraph(CompatibleBlueprint, TargetGraph->Schema);
				ensureMsgf( CompatibleOuter->Schema != nullptr, TEXT("Invalid schema for template graph (from '%s :: %s')."),
						*TargetBlueprint->GetName(), *TargetGraph->GetName() );

				if (CompatibleBlueprint != TargetBlueprint)
				{
					int32 const ApproxGraphSize = ApproximateMemFootprint(CompatibleOuter);
					ActiveMemFootprint   += ApproxGraphSize;
					ApproximateObjectMem += ApproxGraphSize;
				}				
			}
		}

		if (CompatibleOuter != nullptr)
		{
			TemplateNode = NodeSpawner->Invoke(CompatibleOuter, IBlueprintNodeBinder::FBindingSet(), FVector2D::ZeroVector);
			if (!bIsOverMemCap && !CacheTemplateNode(NodeSpawner, TemplateNode))
			{
				LogCacheFullMsg();
			}
		}
	}

	return TemplateNode;
}