You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
Setters and getters are native functions called by FProperties when setting property values with *_InContainer functions. Setters and getter function names can be manually specified with Setter = Func and Getter = Func keywords inside of UPROEPRTY macro but they will also be automatically parsed if the name is not explicitly specified if the setter or getter function name matches SetPropertyName and GetPropertyName pattern. The latter behavior can be disabled in UHT's DefaultEngine.ini by setting AutomaticSettersAndGetters=False. ImportText and ExportTextItem functions have been deprecated and should be replaced with *_InContainer or *_Direct variants. #rb Steve.Robb #preflight 6210a377a83e0bcefd03d9e1 #ROBOMERGE-OWNER: marc.audy #ROBOMERGE-AUTHOR: robert.manuszewski #ROBOMERGE-SOURCE: CL 19070318 via CL 19098059 via CL 19104650 via CL 19104661 via CL 19110012 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v921-19075845) [CL 19147839 by marc audy in ue5-main branch]
1090 lines
38 KiB
C++
1090 lines
38 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
MaterialGraphSchema.cpp
|
|
=============================================================================*/
|
|
|
|
#include "MaterialGraph/MaterialGraphSchema.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "UObject/UnrealType.h"
|
|
#include "UObject/PropertyPortFlags.h"
|
|
#include "Textures/SlateIcon.h"
|
|
#include "Framework/Commands/UIAction.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "ToolMenus.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "HAL/IConsoleManager.h"
|
|
#include "Materials/MaterialExpression.h"
|
|
#include "Materials/MaterialExpressionMaterialFunctionCall.h"
|
|
#include "Materials/MaterialFunction.h"
|
|
#include "MaterialGraph/MaterialGraph.h"
|
|
#include "Engine/Texture.h"
|
|
#include "MaterialGraph/MaterialGraphNode_Base.h"
|
|
#include "MaterialGraph/MaterialGraphNode_Comment.h"
|
|
#include "MaterialGraph/MaterialGraphNode.h"
|
|
#include "MaterialGraph/MaterialGraphNode_Root.h"
|
|
#include "Materials/MaterialParameterCollection.h"
|
|
|
|
#include "Materials/MaterialExpressionCollectionParameter.h"
|
|
#include "Materials/MaterialExpressionComment.h"
|
|
#include "Materials/MaterialExpressionComposite.h"
|
|
#include "Materials/MaterialExpressionPinBase.h"
|
|
#include "Materials/MaterialExpressionFunctionInput.h"
|
|
#include "Materials/MaterialExpressionTextureSample.h"
|
|
#include "Materials/MaterialExpressionFunctionOutput.h"
|
|
#include "Materials/MaterialExpressionReroute.h"
|
|
#include "Materials/MaterialExpressionNamedReroute.h"
|
|
|
|
#include "ScopedTransaction.h"
|
|
#include "MaterialEditorUtilities.h"
|
|
#include "GraphEditorActions.h"
|
|
#include "AssetRegistryModule.h"
|
|
#include "MaterialEditorActions.h"
|
|
#include "MaterialGraphNode_Knot.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "MaterialGraphSchema"
|
|
|
|
int32 UMaterialGraphSchema::CurrentCacheRefreshID = 0;
|
|
|
|
////////////////////////////////////////
|
|
// FMaterialGraphSchemaAction_NewNode //
|
|
|
|
UEdGraphNode* FMaterialGraphSchemaAction_NewNode::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode/* = true*/)
|
|
{
|
|
check(MaterialExpressionClass);
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MaterialEditorNewExpression", "Material Editor: New Expression"));
|
|
|
|
UMaterialExpression* NewExpression = FMaterialEditorUtilities::CreateNewMaterialExpression(ParentGraph, MaterialExpressionClass, Location, bSelectNewNode, /*bAutoAssignResource*/true);
|
|
|
|
if (NewExpression)
|
|
{
|
|
if (MaterialExpressionClass == UMaterialExpressionFunctionInput::StaticClass() && FromPin)
|
|
{
|
|
// Set this to be an input of the type we dragged from
|
|
SetFunctionInputType(CastChecked<UMaterialExpressionFunctionInput>(NewExpression), UMaterialGraphSchema::GetMaterialValueType(FromPin));
|
|
}
|
|
|
|
NewExpression->GraphNode->AutowireNewNode(FromPin);
|
|
|
|
return NewExpression->GraphNode;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void FMaterialGraphSchemaAction_NewNode::SetFunctionInputType(UMaterialExpressionFunctionInput* FunctionInput, uint32 MaterialValueType) const
|
|
{
|
|
switch (MaterialValueType)
|
|
{
|
|
case MCT_Float:
|
|
case MCT_Float1:
|
|
FunctionInput->InputType = FunctionInput_Scalar;
|
|
break;
|
|
case MCT_Float2:
|
|
FunctionInput->InputType = FunctionInput_Vector2;
|
|
break;
|
|
case MCT_Float3:
|
|
FunctionInput->InputType = FunctionInput_Vector3;
|
|
break;
|
|
case MCT_Float4:
|
|
FunctionInput->InputType = FunctionInput_Vector4;
|
|
break;
|
|
case MCT_Texture:
|
|
case MCT_Texture2D:
|
|
FunctionInput->InputType = FunctionInput_Texture2D;
|
|
break;
|
|
case MCT_TextureCube:
|
|
FunctionInput->InputType = FunctionInput_TextureCube;
|
|
break;
|
|
case MCT_Texture2DArray:
|
|
FunctionInput->InputType = FunctionInput_Texture2DArray;
|
|
break;
|
|
case MCT_TextureExternal:
|
|
FunctionInput->InputType = FunctionInput_TextureExternal;
|
|
break;
|
|
case MCT_VolumeTexture:
|
|
FunctionInput->InputType = FunctionInput_VolumeTexture;
|
|
break;
|
|
case MCT_StaticBool:
|
|
FunctionInput->InputType = FunctionInput_StaticBool;
|
|
break;
|
|
case MCT_MaterialAttributes:
|
|
FunctionInput->InputType = FunctionInput_MaterialAttributes;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////
|
|
// FMaterialGraphSchemaAction_NewFunctionCall //
|
|
|
|
UEdGraphNode* FMaterialGraphSchemaAction_NewFunctionCall::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode/* = true*/)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MaterialEditorNewFunctionCall", "Material Editor: New Function Call"));
|
|
|
|
UMaterialExpressionMaterialFunctionCall* FunctionNode = CastChecked<UMaterialExpressionMaterialFunctionCall>(
|
|
FMaterialEditorUtilities::CreateNewMaterialExpression(
|
|
ParentGraph, UMaterialExpressionMaterialFunctionCall::StaticClass(), Location, bSelectNewNode, /*bAutoAssignResource*/false));
|
|
|
|
if (!FunctionNode->MaterialFunction)
|
|
{
|
|
UMaterialFunction* MaterialFunction = LoadObject<UMaterialFunction>(NULL, *FunctionPath, NULL, 0, NULL);
|
|
UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(ParentGraph);
|
|
if(FunctionNode->SetMaterialFunction(MaterialFunction))
|
|
{
|
|
FunctionNode->PostEditChange();
|
|
FMaterialEditorUtilities::UpdateSearchResults(ParentGraph);
|
|
FunctionNode->GraphNode->AutowireNewNode(FromPin);
|
|
return FunctionNode->GraphNode;
|
|
}
|
|
else
|
|
{
|
|
FMaterialEditorUtilities::AddToSelection(ParentGraph, FunctionNode);
|
|
FMaterialEditorUtilities::DeleteSelectedNodes(ParentGraph);
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/////////////////////////////////////////////
|
|
// FMaterialGraphSchemaAction_NewComposite //
|
|
|
|
UEdGraphNode* FMaterialGraphSchemaAction_NewComposite::PerformAction(UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode)
|
|
{
|
|
return SpawnNode(ParentGraph, Location);
|
|
}
|
|
|
|
UEdGraphNode* FMaterialGraphSchemaAction_NewComposite::SpawnNode(UEdGraph* ParentGraph, const FVector2D Location)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MaterialEditorNewComposite", "Material Editor: New Composite"));
|
|
|
|
UMaterialExpressionComposite* NewComposite = FMaterialEditorUtilities::CreateNewMaterialExpressionComposite(ParentGraph, Location);
|
|
|
|
if (NewComposite)
|
|
{
|
|
return NewComposite->GraphNode;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
///////////////////////////////////////////
|
|
// FMaterialGraphSchemaAction_NewComment //
|
|
|
|
UEdGraphNode* FMaterialGraphSchemaAction_NewComment::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode/* = true*/)
|
|
{
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MaterialEditorNewComment", "Material Editor: New Comment") );
|
|
|
|
UMaterialExpressionComment* NewComment = FMaterialEditorUtilities::CreateNewMaterialExpressionComment(ParentGraph, Location);
|
|
|
|
if (NewComment)
|
|
{
|
|
return NewComment->GraphNode;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////
|
|
// FMaterialGraphSchemaAction_NewNamedRerouteUsage //
|
|
|
|
UEdGraphNode* FMaterialGraphSchemaAction_NewNamedRerouteUsage::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode /*= true*/)
|
|
{
|
|
check(Declaration);
|
|
|
|
const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MaterialEditorNewNamedRerouteUsage", "Material Editor: New Named Reroute Usage") );
|
|
|
|
UMaterialExpression* NewExpression = FMaterialEditorUtilities::CreateNewMaterialExpression(ParentGraph, UMaterialExpressionNamedRerouteUsage::StaticClass(), Location, bSelectNewNode, /*bAutoAssignResource*/true);
|
|
|
|
if (NewExpression)
|
|
{
|
|
auto* Usage = CastChecked<UMaterialExpressionNamedRerouteUsage>(NewExpression);
|
|
Usage->Declaration = Declaration;
|
|
Usage->DeclarationGuid = Declaration->VariableGuid;
|
|
NewExpression->GraphNode->AutowireNewNode(FromPin);
|
|
return NewExpression->GraphNode;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////////////////////////
|
|
// FMaterialGraphSchemaAction_Paste //
|
|
|
|
UEdGraphNode* FMaterialGraphSchemaAction_Paste::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2D Location, bool bSelectNewNode/* = true*/)
|
|
{
|
|
FMaterialEditorUtilities::PasteNodesHere(ParentGraph, Location);
|
|
return NULL;
|
|
}
|
|
|
|
//////////////////////////
|
|
// UMaterialGraphSchema //
|
|
|
|
const FName UMaterialGraphSchema::PC_Mask(TEXT("mask"));
|
|
const FName UMaterialGraphSchema::PC_Required(TEXT("required"));
|
|
const FName UMaterialGraphSchema::PC_Optional(TEXT("optional"));
|
|
const FName UMaterialGraphSchema::PC_MaterialInput(TEXT("materialinput"));
|
|
const FName UMaterialGraphSchema::PC_Exec(TEXT("exec"));
|
|
|
|
const FName UMaterialGraphSchema::PSC_Red(TEXT("red"));
|
|
const FName UMaterialGraphSchema::PSC_Green(TEXT("green"));
|
|
const FName UMaterialGraphSchema::PSC_Blue(TEXT("blue"));
|
|
const FName UMaterialGraphSchema::PSC_Alpha(TEXT("alpha"));
|
|
const FName UMaterialGraphSchema::PSC_RGBA(TEXT("rgba"));
|
|
|
|
const FName UMaterialGraphSchema::PN_Execute("execute");
|
|
|
|
const FLinearColor UMaterialGraphSchema::ActivePinColor = FLinearColor::White;
|
|
const FLinearColor UMaterialGraphSchema::InactivePinColor = FLinearColor(0.05f, 0.05f, 0.05f);
|
|
const FLinearColor UMaterialGraphSchema::AlphaPinColor = FLinearColor(0.5f, 0.5f, 0.5f);
|
|
|
|
UMaterialGraphSchema::UMaterialGraphSchema(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
}
|
|
|
|
void UMaterialGraphSchema::SelectAllInputNodes(UEdGraph* Graph, UEdGraphPin* InGraphPin)
|
|
{
|
|
TArray<UEdGraphPin*> AllPins = InGraphPin->LinkedTo;
|
|
|
|
if (AllPins.Num() == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (UEdGraphPin* Pin : AllPins)
|
|
{
|
|
UMaterialGraphNode* MaterialNode = Cast<UMaterialGraphNode>(Pin->GetOwningNode());
|
|
FMaterialEditorUtilities::AddToSelection(Graph, MaterialNode->MaterialExpression);
|
|
|
|
TArray<UEdGraphPin*> LinkedPins = Pin->GetOwningNode()->GetAllPins();
|
|
for (UEdGraphPin* InputPin : LinkedPins)
|
|
{
|
|
if (InputPin->Direction == EEdGraphPinDirection::EGPD_Output)
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
SelectAllInputNodes(Graph, InputPin);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMaterialGraphSchema::GetBreakLinkToSubMenuActions(class UToolMenu* Menu, UEdGraphPin* InGraphPin) const
|
|
{
|
|
FToolMenuSection& Section = Menu->FindOrAddSection("MaterialGraphSchemaPinActions");
|
|
|
|
// Make sure we have a unique name for every entry in the list
|
|
TMap< FString, uint32 > LinkTitleCount;
|
|
|
|
// Add all the links we could break from
|
|
for(TArray<class UEdGraphPin*>::TConstIterator Links(InGraphPin->LinkedTo); Links; ++Links)
|
|
{
|
|
UEdGraphPin* Pin = *Links;
|
|
FString TitleString = Pin->GetOwningNode()->GetNodeTitle(ENodeTitleType::ListView).ToString();
|
|
FText Title = FText::FromString( TitleString );
|
|
if ( Pin->PinName != TEXT("") )
|
|
{
|
|
TitleString = FString::Printf(TEXT("%s (%s)"), *TitleString, *Pin->PinName.ToString());
|
|
|
|
// Add name of connection if possible
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("NodeTitle"), Title );
|
|
Args.Add( TEXT("PinName"), Pin->GetDisplayName() );
|
|
Title = FText::Format( LOCTEXT("BreakDescPin", "{NodeTitle} ({PinName})"), Args );
|
|
}
|
|
|
|
uint32 &Count = LinkTitleCount.FindOrAdd(TitleString);
|
|
|
|
FText Description;
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("NodeTitle"), Title );
|
|
Args.Add( TEXT("NumberOfNodes"), Count );
|
|
|
|
if ( Count == 0 )
|
|
{
|
|
Description = FText::Format( LOCTEXT("BreakDesc", "Break link to {NodeTitle}"), Args );
|
|
}
|
|
else
|
|
{
|
|
Description = FText::Format( LOCTEXT("BreakDescMulti", "Break link to {NodeTitle} ({NumberOfNodes})"), Args );
|
|
}
|
|
++Count;
|
|
Section.AddMenuEntry(NAME_None, Description, Description, FSlateIcon(), FUIAction(
|
|
FExecuteAction::CreateUObject((UMaterialGraphSchema*const)this, &UMaterialGraphSchema::BreakSinglePinLink, const_cast< UEdGraphPin* >(InGraphPin), *Links) ) );
|
|
}
|
|
}
|
|
|
|
void UMaterialGraphSchema::OnConnectToFunctionOutput(UEdGraphPin* InGraphPin, UEdGraphPin* InFuncPin)
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_CreateConnection", "Create Pin Link") );
|
|
|
|
TryCreateConnection(InGraphPin, InFuncPin);
|
|
}
|
|
|
|
void UMaterialGraphSchema::OnConnectToMaterial(class UEdGraphPin* InGraphPin, int32 ConnIndex)
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_CreateConnection", "Create Pin Link") );
|
|
|
|
UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(InGraphPin->GetOwningNode()->GetGraph());
|
|
|
|
TryCreateConnection(InGraphPin, MaterialGraph->RootNode->GetInputPin(ConnIndex));
|
|
}
|
|
|
|
void UMaterialGraphSchema::GetPaletteActions(FGraphActionMenuBuilder& ActionMenuBuilder, const FString& CategoryName, bool bMaterialFunction) const
|
|
{
|
|
if (CategoryName != TEXT("Functions"))
|
|
{
|
|
FMaterialEditorUtilities::GetMaterialExpressionActions(ActionMenuBuilder, bMaterialFunction);
|
|
GetCommentAction(ActionMenuBuilder);
|
|
}
|
|
if (CategoryName != TEXT("Expressions"))
|
|
{
|
|
GetMaterialFunctionActions(ActionMenuBuilder);
|
|
}
|
|
}
|
|
|
|
bool UMaterialGraphSchema::ConnectionCausesLoop(const UEdGraphPin* InputPin, const UEdGraphPin* OutputPin) const
|
|
{
|
|
if (UMaterialGraphNode* OutputNode = Cast<UMaterialGraphNode>(OutputPin->GetOwningNode()))
|
|
{
|
|
TArray<UMaterialExpression*> InputExpressions;
|
|
OutputNode->MaterialExpression->GetAllInputExpressions(InputExpressions);
|
|
|
|
if (UMaterialGraphNode* InputNode = Cast<UMaterialGraphNode>(InputPin->GetOwningNode()))
|
|
{
|
|
return InputExpressions.Contains(InputNode->MaterialExpression);
|
|
}
|
|
}
|
|
|
|
// Simple connection to root node
|
|
return false;
|
|
}
|
|
|
|
bool UMaterialGraphSchema::ArePinsCompatible_Internal(const UEdGraphPin* InputPin, const UEdGraphPin* OutputPin, FText& ResponseMessage) const
|
|
{
|
|
uint32 InputType = GetMaterialValueType(InputPin);
|
|
uint32 OutputType = GetMaterialValueType(OutputPin);
|
|
|
|
bool bPinsCompatible = CanConnectMaterialValueTypes(InputType, OutputType);
|
|
if (!bPinsCompatible)
|
|
{
|
|
TArray<FText> InputDescriptions;
|
|
TArray<FText> OutputDescriptions;
|
|
GetMaterialValueTypeDescriptions(InputType, InputDescriptions);
|
|
GetMaterialValueTypeDescriptions(OutputType, OutputDescriptions);
|
|
|
|
FString CombinedInputDescription;
|
|
FString CombinedOutputDescription;
|
|
for (int32 Index = 0; Index < InputDescriptions.Num(); ++Index)
|
|
{
|
|
if ( CombinedInputDescription.Len() > 0 )
|
|
{
|
|
CombinedInputDescription += TEXT(", ");
|
|
}
|
|
CombinedInputDescription += InputDescriptions[Index].ToString();
|
|
}
|
|
for (int32 Index = 0; Index < OutputDescriptions.Num(); ++Index)
|
|
{
|
|
if ( CombinedOutputDescription.Len() > 0 )
|
|
{
|
|
CombinedOutputDescription += TEXT(", ");
|
|
}
|
|
CombinedOutputDescription += OutputDescriptions[Index].ToString();
|
|
}
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("InputType"), FText::FromString(CombinedInputDescription) );
|
|
Args.Add( TEXT("OutputType"), FText::FromString(CombinedOutputDescription) );
|
|
ResponseMessage = FText::Format( LOCTEXT("IncompatibleDesc", "{OutputType} is not compatible with {InputType}"), Args );
|
|
}
|
|
|
|
return bPinsCompatible;
|
|
}
|
|
|
|
uint32 UMaterialGraphSchema::GetMaterialValueType(const UEdGraphPin* MaterialPin)
|
|
{
|
|
const UMaterialGraphNode_Base* OwningNode = CastChecked<UMaterialGraphNode_Base>(MaterialPin->GetOwningNode());
|
|
if (MaterialPin->Direction == EGPD_Output)
|
|
{
|
|
return OwningNode->GetOutputType(MaterialPin);
|
|
}
|
|
else
|
|
{
|
|
return OwningNode->GetInputType(MaterialPin);
|
|
}
|
|
}
|
|
|
|
void UMaterialGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const
|
|
{
|
|
const UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(ContextMenuBuilder.CurrentGraph);
|
|
|
|
// Run through all nodes and add any menu items they want to add
|
|
Super::GetGraphContextActions(ContextMenuBuilder);
|
|
|
|
// Get the Context Actions from Material Editor Module
|
|
FMaterialEditorUtilities::GetMaterialExpressionActions(ContextMenuBuilder, MaterialGraph->MaterialFunction != NULL);
|
|
|
|
// Get the Material Functions as well
|
|
GetMaterialFunctionActions(ContextMenuBuilder);
|
|
|
|
GetCommentAction(ContextMenuBuilder, ContextMenuBuilder.CurrentGraph);
|
|
|
|
GetNamedRerouteActions(ContextMenuBuilder, ContextMenuBuilder.CurrentGraph);
|
|
|
|
// Add Paste here if appropriate
|
|
if (!ContextMenuBuilder.FromPin && FMaterialEditorUtilities::CanPasteNodes(ContextMenuBuilder.CurrentGraph))
|
|
{
|
|
const FText PasteDesc = LOCTEXT("PasteDesc", "Paste Here");
|
|
const FText PasteToolTip = LOCTEXT("PasteToolTip", "Pastes copied items at this location.");
|
|
TSharedPtr<FMaterialGraphSchemaAction_Paste> PasteAction(new FMaterialGraphSchemaAction_Paste(FText::GetEmpty(), PasteDesc, PasteToolTip, 0));
|
|
ContextMenuBuilder.AddAction(PasteAction);
|
|
}
|
|
}
|
|
|
|
void UMaterialGraphSchema::GetContextMenuActions(UToolMenu* Menu, UGraphNodeContextMenuContext* Context) const
|
|
{
|
|
if (Context && Context->Pin)
|
|
{
|
|
const UEdGraphPin* InGraphPin = Context->Pin;
|
|
const UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(Context->Graph);
|
|
|
|
// add menu items to expression output for material connection
|
|
if ( InGraphPin->Direction == EGPD_Output )
|
|
{
|
|
{
|
|
FToolMenuSection& Section = Menu->AddSection("MaterialEditorMenuConnector2");
|
|
// If we are editing a material function, display options to connect to function outputs
|
|
if (MaterialGraph->MaterialFunction)
|
|
{
|
|
for (int32 Index = 0; Index < MaterialGraph->Nodes.Num(); Index++)
|
|
{
|
|
UMaterialGraphNode* GraphNode = Cast<UMaterialGraphNode>(MaterialGraph->Nodes[Index]);
|
|
if (GraphNode)
|
|
{
|
|
UMaterialExpressionFunctionOutput* FunctionOutput = Cast<UMaterialExpressionFunctionOutput>(GraphNode->MaterialExpression);
|
|
if (FunctionOutput)
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("Name"), FText::FromName( FunctionOutput->OutputName ));
|
|
const FText Label = FText::Format( LOCTEXT( "ConnectToFunction", "Connect To {Name}" ), Arguments );
|
|
const FText ToolTip = FText::Format( LOCTEXT( "ConnectToFunctionTooltip", "Connects to the function output {Name}" ), Arguments );
|
|
Section.AddMenuEntry(
|
|
NAME_None,
|
|
Label,
|
|
ToolTip,
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateUObject(const_cast<UMaterialGraphSchema*>(this), &UMaterialGraphSchema::OnConnectToFunctionOutput, const_cast< UEdGraphPin* >(InGraphPin), GraphNode->GetInputPin(0))));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int32 Index = 0; Index < MaterialGraph->MaterialInputs.Num(); ++Index)
|
|
{
|
|
if(MaterialGraph->MaterialInputs[Index].IsVisiblePin(MaterialGraph->Material))
|
|
{
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("Name"), MaterialGraph->MaterialInputs[Index].GetName());
|
|
const FText Label = FText::Format( LOCTEXT( "ConnectToInput", "Connect To {Name}" ), Arguments );
|
|
const FText ToolTip = FText::Format( LOCTEXT( "ConnectToInputTooltip", "Connects to the material input {Name}" ), Arguments );
|
|
Section.AddMenuEntry(
|
|
NAME_None,
|
|
Label,
|
|
ToolTip,
|
|
FSlateIcon(),
|
|
FUIAction(FExecuteAction::CreateUObject(const_cast<UMaterialGraphSchema*>(this), &UMaterialGraphSchema::OnConnectToMaterial, const_cast< UEdGraphPin* >(InGraphPin), Index)));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Moved all functionality to relevant node classes
|
|
}
|
|
|
|
Super::GetContextMenuActions(Menu, Context);
|
|
}
|
|
|
|
static TAutoConsoleVariable<int32> CVarPreventInvalidMaterialConnections(
|
|
TEXT("r.PreventInvalidMaterialConnections"),
|
|
1,
|
|
TEXT("Controls whether users can make connections in the material editor if the system\n")
|
|
TEXT("determines that they may cause compile errors\n")
|
|
TEXT("0: Allow all connections\n")
|
|
TEXT("1: Prevent invalid connections"),
|
|
ECVF_Cheat);
|
|
|
|
const FPinConnectionResponse UMaterialGraphSchema::CanCreateConnection(const UEdGraphPin* A, const UEdGraphPin* B) const
|
|
{
|
|
bool bPreventInvalidConnections = CVarPreventInvalidMaterialConnections.GetValueOnGameThread() != 0;
|
|
|
|
// Make sure the pins are not on the same node
|
|
if (A->GetOwningNode() == B->GetOwningNode())
|
|
{
|
|
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionSameNode", "Both are on the same node"));
|
|
}
|
|
|
|
// Compare the directions
|
|
const UEdGraphPin* InputPin = NULL;
|
|
const UEdGraphPin* OutputPin = NULL;
|
|
|
|
if (!CategorizePinsByDirection(A, B, /*out*/ InputPin, /*out*/ OutputPin))
|
|
{
|
|
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionIncompatible", "Directions are not compatible"));
|
|
}
|
|
|
|
// Check for new and existing loops
|
|
FText ResponseMessage;
|
|
if (ConnectionCausesLoop(InputPin, OutputPin))
|
|
{
|
|
ResponseMessage = LOCTEXT("ConnectionLoop", "Connection could cause loop");
|
|
// TODO: re-enable this if loops are going to be removed completely
|
|
//return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionLoop", "Connection would cause loop").ToString());
|
|
}
|
|
|
|
// Check for incompatible pins and get description if they cannot connect
|
|
if (!ArePinsCompatible_Internal(InputPin, OutputPin, ResponseMessage) && bPreventInvalidConnections)
|
|
{
|
|
return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, ResponseMessage);
|
|
}
|
|
|
|
// For non-exec pins, break existing connections on inputs only - multiple output connections are acceptable
|
|
if (InputPin->LinkedTo.Num() > 0)
|
|
{
|
|
const uint32 InputType = GetMaterialValueType(InputPin);
|
|
if (!(InputType & MCT_Execution))
|
|
{
|
|
ECanCreateConnectionResponse ReplyBreakOutputs;
|
|
if (InputPin == A)
|
|
{
|
|
ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_A;
|
|
}
|
|
else
|
|
{
|
|
ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_B;
|
|
}
|
|
if (ResponseMessage.IsEmpty())
|
|
{
|
|
ResponseMessage = LOCTEXT("ConnectionReplace", "Replace existing connections");
|
|
}
|
|
return FPinConnectionResponse(ReplyBreakOutputs, ResponseMessage);
|
|
}
|
|
}
|
|
|
|
// For exec pins, reverse is true - multiple input connections are acceptable
|
|
if (OutputPin->LinkedTo.Num() > 0)
|
|
{
|
|
const uint32 OutputType = GetMaterialValueType(InputPin);
|
|
if (OutputType & MCT_Execution)
|
|
{
|
|
ECanCreateConnectionResponse ReplyBreakInputs;
|
|
if (OutputPin == A)
|
|
{
|
|
ReplyBreakInputs = CONNECT_RESPONSE_BREAK_OTHERS_A;
|
|
}
|
|
else
|
|
{
|
|
ReplyBreakInputs = CONNECT_RESPONSE_BREAK_OTHERS_B;
|
|
}
|
|
if (ResponseMessage.IsEmpty())
|
|
{
|
|
ResponseMessage = LOCTEXT("ConnectionReplace", "Replace existing connections");
|
|
}
|
|
return FPinConnectionResponse(ReplyBreakInputs, ResponseMessage);
|
|
}
|
|
}
|
|
|
|
return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, ResponseMessage);
|
|
}
|
|
|
|
bool UMaterialGraphSchema::TryCreateConnection(UEdGraphPin* A, UEdGraphPin* B) const
|
|
{
|
|
bool bModified = UEdGraphSchema::TryCreateConnection(A, B);
|
|
|
|
if (bModified)
|
|
{
|
|
FMaterialEditorUtilities::UpdateMaterialAfterGraphChange(A->GetOwningNode()->GetGraph());
|
|
}
|
|
|
|
return bModified;
|
|
}
|
|
|
|
FLinearColor UMaterialGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const
|
|
{
|
|
if (PinType.PinCategory == PC_Mask)
|
|
{
|
|
if (PinType.PinSubCategory == PSC_Red)
|
|
{
|
|
return FLinearColor::Red;
|
|
}
|
|
else if (PinType.PinSubCategory == PSC_Green)
|
|
{
|
|
return FLinearColor::Green;
|
|
}
|
|
else if (PinType.PinSubCategory == PSC_Blue)
|
|
{
|
|
return FLinearColor::Blue;
|
|
}
|
|
else if (PinType.PinSubCategory == PSC_Alpha)
|
|
{
|
|
return AlphaPinColor;
|
|
}
|
|
}
|
|
else if (PinType.PinCategory == PC_Required)
|
|
{
|
|
return ActivePinColor;
|
|
}
|
|
else if (PinType.PinCategory == PC_Optional)
|
|
{
|
|
return InactivePinColor;
|
|
}
|
|
|
|
return ActivePinColor;
|
|
}
|
|
|
|
void UMaterialGraphSchema::BreakNodeLinks(UEdGraphNode& TargetNode) const
|
|
{
|
|
bool bHasLinksToBreak = false;
|
|
for (auto PinIt = TargetNode.Pins.CreateConstIterator(); PinIt; ++PinIt)
|
|
{
|
|
UEdGraphPin* Pin = *PinIt;
|
|
for (auto LinkIt = Pin->LinkedTo.CreateConstIterator(); LinkIt; ++LinkIt)
|
|
{
|
|
if (*LinkIt)
|
|
{
|
|
bHasLinksToBreak = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
Super::BreakNodeLinks(TargetNode);
|
|
|
|
if (bHasLinksToBreak)
|
|
{
|
|
FMaterialEditorUtilities::UpdateMaterialAfterGraphChange(TargetNode.GetGraph());
|
|
}
|
|
}
|
|
|
|
void UMaterialGraphSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_BreakPinLinks", "Break Pin Links") );
|
|
|
|
bool bHasLinksToBreak = false;
|
|
for (auto LinkIt = TargetPin.LinkedTo.CreateConstIterator(); LinkIt; ++LinkIt)
|
|
{
|
|
if (*LinkIt)
|
|
{
|
|
bHasLinksToBreak = true;
|
|
}
|
|
}
|
|
|
|
Super::BreakPinLinks(TargetPin, bSendsNodeNotifcation);
|
|
|
|
// if this would notify the node then we need to re-compile material
|
|
if (bSendsNodeNotifcation && bHasLinksToBreak)
|
|
{
|
|
FMaterialEditorUtilities::UpdateMaterialAfterGraphChange(TargetPin.GetOwningNode()->GetGraph());
|
|
}
|
|
}
|
|
|
|
void UMaterialGraphSchema::BreakSinglePinLink(UEdGraphPin* SourcePin, UEdGraphPin* TargetPin) const
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_BreakSinglePinLink", "Break Pin Link") );
|
|
|
|
bool bHasLinkToBreak = false;
|
|
for (auto LinkIt = SourcePin->LinkedTo.CreateConstIterator(); LinkIt; ++LinkIt)
|
|
{
|
|
if (*LinkIt == TargetPin)
|
|
{
|
|
bHasLinkToBreak = true;
|
|
}
|
|
}
|
|
|
|
Super::BreakSinglePinLink(SourcePin, TargetPin);
|
|
|
|
if (bHasLinkToBreak)
|
|
{
|
|
FMaterialEditorUtilities::UpdateMaterialAfterGraphChange(SourcePin->GetOwningNode()->GetGraph());
|
|
}
|
|
}
|
|
|
|
bool UMaterialGraphSchema::CanEncapuslateNode(UEdGraphNode const& TestNode) const
|
|
{
|
|
if (TestNode.IsA(UMaterialGraphNode_Comment::StaticClass()))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// Disallow output nodes from encapsulation, everything else (including parameters) is fair game for materials.
|
|
const UMaterialGraphNode* MaterialGraphNode = Cast<UMaterialGraphNode>(&TestNode);
|
|
|
|
return MaterialGraphNode && MaterialGraphNode->MaterialExpression
|
|
&& !MaterialGraphNode->MaterialExpression->IsA(UMaterialExpressionFunctionOutput::StaticClass())
|
|
&& !MaterialGraphNode->MaterialExpression->IsA(UMaterialExpressionPinBase::StaticClass())
|
|
&& !TestNode.IsA(UMaterialGraphNode_Root::StaticClass());
|
|
}
|
|
|
|
void UMaterialGraphSchema::DroppedAssetsOnGraph(const TArray<struct FAssetData>& Assets, const FVector2D& GraphPosition, UEdGraph* Graph) const
|
|
{
|
|
UMaterialGraph* MaterialGraph = CastChecked<UMaterialGraph>(Graph);
|
|
const int32 LocOffsetBetweenNodes = 32;
|
|
|
|
FVector2D ExpressionPosition = GraphPosition;
|
|
|
|
for (int32 AssetIdx = 0; AssetIdx < Assets.Num(); ++AssetIdx)
|
|
{
|
|
bool bAddedNode = false;
|
|
UObject* Asset = Assets[AssetIdx].GetAsset();
|
|
UClass* MaterialExpressionClass = Cast<UClass>(Asset);
|
|
UMaterialFunctionInterface* Func = Cast<UMaterialFunctionInterface>(Asset);
|
|
UTexture* Tex = Cast<UTexture>(Asset);
|
|
UMaterialParameterCollection* ParameterCollection = Cast<UMaterialParameterCollection>(Asset);
|
|
|
|
if (MaterialExpressionClass && MaterialExpressionClass->IsChildOf(UMaterialExpression::StaticClass()))
|
|
{
|
|
FMaterialEditorUtilities::CreateNewMaterialExpression(Graph, MaterialExpressionClass, ExpressionPosition, true, true);
|
|
bAddedNode = true;
|
|
}
|
|
else if ( Func )
|
|
{
|
|
UMaterialExpressionMaterialFunctionCall* FunctionNode = CastChecked<UMaterialExpressionMaterialFunctionCall>(
|
|
FMaterialEditorUtilities::CreateNewMaterialExpression(Graph, UMaterialExpressionMaterialFunctionCall::StaticClass(), ExpressionPosition, true, false));
|
|
|
|
if (!FunctionNode->MaterialFunction)
|
|
{
|
|
if(FunctionNode->SetMaterialFunction(Func))
|
|
{
|
|
FunctionNode->PostEditChange();
|
|
FMaterialEditorUtilities::UpdateSearchResults(Graph);
|
|
}
|
|
else
|
|
{
|
|
FMaterialEditorUtilities::AddToSelection(Graph, FunctionNode);
|
|
FMaterialEditorUtilities::DeleteSelectedNodes(Graph);
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bAddedNode = true;
|
|
}
|
|
else if ( Tex )
|
|
{
|
|
UMaterialExpressionTextureSample* TextureSampleNode = CastChecked<UMaterialExpressionTextureSample>(
|
|
FMaterialEditorUtilities::CreateNewMaterialExpression(Graph, UMaterialExpressionTextureSample::StaticClass(), ExpressionPosition, true, true) );
|
|
TextureSampleNode->Texture = Tex;
|
|
TextureSampleNode->AutoSetSampleType();
|
|
|
|
FMaterialEditorUtilities::ForceRefreshExpressionPreviews(Graph);
|
|
|
|
bAddedNode = true;
|
|
}
|
|
else if ( ParameterCollection )
|
|
{
|
|
UMaterialExpressionCollectionParameter* CollectionParameterNode = CastChecked<UMaterialExpressionCollectionParameter>(
|
|
FMaterialEditorUtilities::CreateNewMaterialExpression(Graph, UMaterialExpressionCollectionParameter::StaticClass(), ExpressionPosition, true, true) );
|
|
CollectionParameterNode->Collection = ParameterCollection;
|
|
|
|
FMaterialEditorUtilities::ForceRefreshExpressionPreviews(Graph);
|
|
|
|
bAddedNode = true;
|
|
}
|
|
|
|
if ( bAddedNode )
|
|
{
|
|
ExpressionPosition.X += LocOffsetBetweenNodes;
|
|
ExpressionPosition.Y += LocOffsetBetweenNodes;
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 UMaterialGraphSchema::GetNodeSelectionCount(const UEdGraph* Graph) const
|
|
{
|
|
return FMaterialEditorUtilities::GetNumberOfSelectedNodes(Graph);
|
|
}
|
|
|
|
TSharedPtr<FEdGraphSchemaAction> UMaterialGraphSchema::GetCreateCommentAction() const
|
|
{
|
|
return TSharedPtr<FEdGraphSchemaAction>(static_cast<FEdGraphSchemaAction*>(new FMaterialGraphSchemaAction_NewComment));
|
|
}
|
|
|
|
void UMaterialGraphSchema::GetMaterialFunctionActions(FGraphActionMenuBuilder& ActionMenuBuilder) const
|
|
{
|
|
// Get type of dragged pin
|
|
uint32 FromPinType = 0;
|
|
if (ActionMenuBuilder.FromPin)
|
|
{
|
|
FromPinType = GetMaterialValueType(ActionMenuBuilder.FromPin);
|
|
}
|
|
|
|
// Load the asset registry module
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
|
|
// Collect a full list of assets with the specified class
|
|
TArray<FAssetData> AssetDataList;
|
|
AssetRegistryModule.Get().GetAssetsByClass(UMaterialFunction::StaticClass()->GetFName(), AssetDataList);
|
|
|
|
for (const FAssetData& AssetData : AssetDataList)
|
|
{
|
|
const bool bExposeToLibrary = AssetData.GetTagValueRef<bool>("bExposeToLibrary");
|
|
|
|
// If this was a function that was selected to be exposed to the library
|
|
if ( bExposeToLibrary )
|
|
{
|
|
if(AssetData.IsAssetLoaded())
|
|
{
|
|
if(AssetData.GetAsset()->GetOutermost() == GetTransientPackage())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!ActionMenuBuilder.FromPin || HasCompatibleConnection(AssetData, FromPinType, ActionMenuBuilder.FromPin->Direction))
|
|
{
|
|
// Gather the relevant information from the asset data
|
|
const FString FunctionPathName = AssetData.ObjectPath.ToString();
|
|
const FText Description = AssetData.GetTagValueRef<FText>("Description");
|
|
TArray<FString> LibraryCategories;
|
|
{
|
|
const FString LibraryCategoriesString = AssetData.GetTagValueRef<FString>("LibraryCategories");
|
|
if ( !LibraryCategoriesString.IsEmpty() )
|
|
{
|
|
if (FArrayProperty* LibraryCategoriesProperty = FindFieldChecked<FArrayProperty>(UMaterialFunction::StaticClass(), TEXT("LibraryCategories")))
|
|
{
|
|
uint8* DestAddr = (uint8*)(&LibraryCategories);
|
|
LibraryCategoriesProperty->ImportText_Direct(*LibraryCategoriesString, DestAddr, NULL, PPF_None, GWarn);
|
|
}
|
|
}
|
|
}
|
|
TArray<FText> LibraryCategoriesText;
|
|
{
|
|
const FString LibraryCategoriesString = AssetData.GetTagValueRef<FString>("LibraryCategoriesText");
|
|
if ( !LibraryCategoriesString.IsEmpty() )
|
|
{
|
|
FArrayProperty* LibraryCategoriesProperty = FindFieldChecked<FArrayProperty>(UMaterialFunction::StaticClass(), GET_MEMBER_NAME_CHECKED(UMaterialFunction, LibraryCategoriesText));
|
|
uint8* DestAddr = (uint8*)(&LibraryCategoriesText);
|
|
LibraryCategoriesProperty->ImportText_Direct(*LibraryCategoriesString, DestAddr, NULL, PPF_None, GWarn);
|
|
}
|
|
|
|
for (const FString& Category : LibraryCategories)
|
|
{
|
|
if (!LibraryCategoriesText.ContainsByPredicate(
|
|
[&Category](const FText& Text)
|
|
{
|
|
return Text.ToString() == Category;
|
|
}
|
|
))
|
|
{
|
|
LibraryCategoriesText.Add(FText::FromString(Category));
|
|
}
|
|
}
|
|
|
|
if ( LibraryCategoriesText.Num() == 0 )
|
|
{
|
|
LibraryCategoriesText.Add( LOCTEXT("UncategorizedMaterialFunction", "Uncategorized") );
|
|
}
|
|
}
|
|
|
|
// Extract the object name from the path
|
|
FString FunctionName = FunctionPathName;
|
|
int32 PeriodIndex = FunctionPathName.Find(TEXT("."), ESearchCase::CaseSensitive, ESearchDir::FromEnd);
|
|
|
|
if (PeriodIndex != INDEX_NONE)
|
|
{
|
|
FunctionName = FunctionPathName.Right(FunctionPathName.Len() - PeriodIndex - 1);
|
|
}
|
|
|
|
// For each category the function should belong to...
|
|
for (const FText& CategoryName : LibraryCategoriesText)
|
|
{
|
|
TSharedPtr<FMaterialGraphSchemaAction_NewFunctionCall> NewFunctionAction(new FMaterialGraphSchemaAction_NewFunctionCall(
|
|
CategoryName,
|
|
FText::FromString(FunctionName),
|
|
Description, 0));
|
|
ActionMenuBuilder.AddAction(NewFunctionAction);
|
|
NewFunctionAction->FunctionPath = FunctionPathName;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void UMaterialGraphSchema::GetCompositeAction(FGraphActionMenuBuilder& ActionMenuBuilder, const UEdGraph* CurrentGraph) const
|
|
{
|
|
if (!ActionMenuBuilder.FromPin)
|
|
{
|
|
const bool bIsManyNodesSelected = CurrentGraph ? (FMaterialEditorUtilities::GetNumberOfSelectedNodes(CurrentGraph) > 0) : false;
|
|
const FText CompositeDesc = LOCTEXT("CompositeDesc", "New Composite");
|
|
const FText CompositeToolTip = LOCTEXT("CompositeToolTip", "Create a composite node that holds a subgraph.");
|
|
TSharedPtr<FMaterialGraphSchemaAction_NewComposite> NewAction(new FMaterialGraphSchemaAction_NewComposite(FText::GetEmpty(), CompositeDesc, CompositeToolTip, 0));
|
|
ActionMenuBuilder.AddAction(NewAction);
|
|
}
|
|
}
|
|
|
|
void UMaterialGraphSchema::GetCommentAction(FGraphActionMenuBuilder& ActionMenuBuilder, const UEdGraph* CurrentGraph) const
|
|
{
|
|
if (!ActionMenuBuilder.FromPin)
|
|
{
|
|
const bool bIsManyNodesSelected = CurrentGraph ? (FMaterialEditorUtilities::GetNumberOfSelectedNodes(CurrentGraph) > 0) : false;
|
|
const FText CommentDesc = LOCTEXT("CommentDesc", "New Comment");
|
|
const FText MultiCommentDesc = LOCTEXT("MultiCommentDesc", "Create Comment from Selection");
|
|
const FText CommentToolTip = LOCTEXT("CommentToolTip", "Creates a comment.");
|
|
const FText MenuDescription = bIsManyNodesSelected ? MultiCommentDesc : CommentDesc;
|
|
TSharedPtr<FMaterialGraphSchemaAction_NewComment> NewAction(new FMaterialGraphSchemaAction_NewComment(FText::GetEmpty(), MenuDescription, CommentToolTip, 0));
|
|
ActionMenuBuilder.AddAction( NewAction );
|
|
}
|
|
}
|
|
|
|
void UMaterialGraphSchema::GetNamedRerouteActions(FGraphActionMenuBuilder& ActionMenuBuilder, const UEdGraph* CurrentGraph) const
|
|
{
|
|
if (CurrentGraph)
|
|
{
|
|
for (UEdGraphNode* GraphNode : CurrentGraph->Nodes)
|
|
{
|
|
if (auto* MaterialGraphNode = Cast<UMaterialGraphNode>(GraphNode))
|
|
{
|
|
if (auto Declaration = Cast<UMaterialExpressionNamedRerouteDeclaration>(MaterialGraphNode->MaterialExpression))
|
|
{
|
|
static const FText Category = LOCTEXT("NamedRerouteCategory", "Named Reroutes");
|
|
const FText Name = FText::FromString(Declaration->Name.ToString());
|
|
const FText Tooltip = FText::Format(LOCTEXT("NamedRerouteTooltip", "Add a usage of {0} here"), Name);
|
|
TSharedPtr<FMaterialGraphSchemaAction_NewNamedRerouteUsage> NewAction(new FMaterialGraphSchemaAction_NewNamedRerouteUsage(Category, Name, Tooltip, 1 /* We want named reroutes to be on top */));
|
|
NewAction->Declaration = Declaration;
|
|
ActionMenuBuilder.AddAction(NewAction);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool UMaterialGraphSchema::HasCompatibleConnection(const FAssetData& FunctionAssetData, uint32 TestType, EEdGraphPinDirection TestDirection) const
|
|
{
|
|
if (TestType != 0)
|
|
{
|
|
uint32 CombinedInputTypes = FunctionAssetData.GetTagValueRef<uint32>(GET_MEMBER_NAME_CHECKED(UMaterialFunctionInterface, CombinedInputTypes));
|
|
uint32 CombinedOutputTypes = FunctionAssetData.GetTagValueRef<uint32>(GET_MEMBER_NAME_CHECKED(UMaterialFunctionInterface, CombinedOutputTypes));
|
|
|
|
if (CombinedOutputTypes == 0)
|
|
{
|
|
// Need to load function to build combined output types
|
|
UMaterialFunctionInterface* MaterialFunction = Cast<UMaterialFunctionInterface>(FunctionAssetData.GetAsset());
|
|
if (MaterialFunction != nullptr)
|
|
{
|
|
CombinedInputTypes = MaterialFunction->CombinedInputTypes;
|
|
CombinedOutputTypes = MaterialFunction->CombinedOutputTypes;
|
|
}
|
|
}
|
|
|
|
if (TestDirection == EGPD_Output)
|
|
{
|
|
if (CanConnectMaterialValueTypes(CombinedInputTypes, TestType))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (CanConnectMaterialValueTypes(TestType, CombinedOutputTypes))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool UMaterialGraphSchema::IsCacheVisualizationOutOfDate(int32 InVisualizationCacheID) const
|
|
{
|
|
return CurrentCacheRefreshID != InVisualizationCacheID;
|
|
}
|
|
|
|
int32 UMaterialGraphSchema::GetCurrentVisualizationCacheID() const
|
|
{
|
|
return CurrentCacheRefreshID;
|
|
}
|
|
|
|
void UMaterialGraphSchema::ForceVisualizationCacheClear() const
|
|
{
|
|
++CurrentCacheRefreshID;
|
|
}
|
|
|
|
void UMaterialGraphSchema::OnPinConnectionDoubleCicked(UEdGraphPin* PinA, UEdGraphPin* PinB, const FVector2D& GraphPosition) const
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("CreateRerouteNodeOnWire", "Create Reroute Node"));
|
|
|
|
//@TODO: This constant is duplicated from inside of SGraphNodeKnot
|
|
const FVector2D NodeSpacerSize(42.0f, 24.0f);
|
|
const FVector2D KnotTopLeft = GraphPosition - (NodeSpacerSize * 0.5f);
|
|
|
|
// Create a new knot
|
|
UEdGraph* ParentGraph = PinA->GetOwningNode()->GetGraph();
|
|
|
|
{
|
|
UMaterialExpression* Expression = FMaterialEditorUtilities::CreateNewMaterialExpression(ParentGraph, UMaterialExpressionReroute::StaticClass(), KnotTopLeft, true, true);
|
|
|
|
// Move the connections across (only notifying the knot, as the other two didn't really change)
|
|
PinA->BreakLinkTo(PinB);
|
|
PinA->MakeLinkTo((PinA->Direction == EGPD_Output) ? CastChecked<UMaterialGraphNode_Knot>(Expression->GraphNode)->GetInputPin() : CastChecked<UMaterialGraphNode_Knot>(Expression->GraphNode)->GetOutputPin());
|
|
PinB->MakeLinkTo((PinB->Direction == EGPD_Output) ? CastChecked<UMaterialGraphNode_Knot>(Expression->GraphNode)->GetInputPin() : CastChecked<UMaterialGraphNode_Knot>(Expression->GraphNode)->GetOutputPin());
|
|
FMaterialEditorUtilities::UpdateMaterialAfterGraphChange(ParentGraph);
|
|
}
|
|
}
|
|
|
|
bool UMaterialGraphSchema::SafeDeleteNodeFromGraph(UEdGraph* Graph, UEdGraphNode* NodeToDelete) const
|
|
{
|
|
if (NodeToDelete == nullptr || Graph == nullptr || NodeToDelete->GetGraph() != Graph)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TArray<UEdGraphNode*> NodesToDelete;
|
|
NodesToDelete.Add(NodeToDelete);
|
|
FMaterialEditorUtilities::DeleteNodes(Graph, NodesToDelete);
|
|
return true;
|
|
}
|
|
|
|
void UMaterialGraphSchema::GetAssetsGraphHoverMessage(const TArray<FAssetData>& Assets, const UEdGraph* HoverGraph, FString& OutTooltipText, bool& OutOkIcon) const
|
|
{
|
|
OutOkIcon = false;
|
|
|
|
for (int32 AssetIdx = 0; AssetIdx < Assets.Num(); ++AssetIdx)
|
|
{
|
|
UObject* Asset = Assets[AssetIdx].GetAsset();
|
|
UClass* MaterialExpressionClass = Cast<UClass>(Asset);
|
|
UMaterialFunctionInterface* Func = Cast<UMaterialFunctionInterface>(Asset);
|
|
UTexture* Tex = Cast<UTexture>(Asset);
|
|
UMaterialParameterCollection* ParameterCollection = Cast<UMaterialParameterCollection>(Asset);
|
|
|
|
if (MaterialExpressionClass && MaterialExpressionClass->IsChildOf(UMaterialExpression::StaticClass()))
|
|
{
|
|
OutOkIcon = true;
|
|
}
|
|
else if (Func)
|
|
{
|
|
OutOkIcon = true;
|
|
}
|
|
else if (Tex)
|
|
{
|
|
OutOkIcon = true;
|
|
}
|
|
else if (ParameterCollection)
|
|
{
|
|
OutOkIcon = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE |