// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. #include "MaterialEditor.h" #include "EngineGlobals.h" #include "Engine/SkeletalMesh.h" #include "AI/NavigationSystemBase.h" #include "Engine/Engine.h" #include "Misc/MessageDialog.h" #include "Modules/ModuleManager.h" #include "SlateOptMacros.h" #include "Widgets/Layout/SSeparator.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Input/SButton.h" #include "EditorStyleSet.h" #include "EdGraph/EdGraph.h" #include "MaterialGraph/MaterialGraph.h" #include "MaterialGraph/MaterialGraphNode_Comment.h" #include "Editor/UnrealEdEngine.h" #include "MaterialEditor/MaterialEditorInstanceConstant.h" #include "Preferences/MaterialEditorOptions.h" #include "MaterialGraph/MaterialGraphNode.h" #include "MaterialGraph/MaterialGraphNode_Root.h" #include "MaterialGraph/MaterialGraphSchema.h" #include "MaterialEditor/PreviewMaterial.h" #include "ThumbnailRendering/SceneThumbnailInfoWithPrimitive.h" #include "Particles/ParticleSystemComponent.h" #include "Materials/MaterialInstance.h" #include "Materials/MaterialInstanceDynamic.h" #include "Materials/MaterialInstanceConstant.h" #include "Materials/MaterialParameterCollection.h" #include "Engine/TextureCube.h" #include "Dialogs/Dialogs.h" #include "UnrealEdGlobals.h" #include "Editor.h" #include "MaterialEditorModule.h" #include "MaterialEditingLibrary.h" #include "HAL/PlatformApplicationMisc.h" #include "Materials/MaterialExpressionBreakMaterialAttributes.h" #include "Materials/MaterialExpressionCollectionParameter.h" #include "Materials/MaterialExpressionComment.h" #include "Materials/MaterialExpressionComponentMask.h" #include "Materials/MaterialExpressionConstant.h" #include "Materials/MaterialExpressionConstant2Vector.h" #include "Materials/MaterialExpressionConstant3Vector.h" #include "Materials/MaterialExpressionConstant4Vector.h" #include "Materials/MaterialExpressionDynamicParameter.h" #include "Materials/MaterialExpressionFontSampleParameter.h" #include "Materials/MaterialExpressionFunctionInput.h" #include "Materials/MaterialExpressionFunctionOutput.h" #include "Materials/MaterialExpressionMaterialAttributeLayers.h" #include "Materials/MaterialExpressionParameter.h" #include "Materials/MaterialExpressionTextureBase.h" #include "Materials/MaterialExpressionTextureSample.h" #include "Materials/MaterialExpressionParticleSubUV.h" #include "Materials/MaterialExpressionScalarParameter.h" #include "Materials/MaterialExpressionStaticComponentMaskParameter.h" #include "Materials/MaterialExpressionTextureSampleParameter.h" #include "Materials/MaterialExpressionTextureObjectParameter.h" #include "Materials/MaterialExpressionTextureObject.h" #include "Materials/MaterialExpressionTextureSampleParameter2D.h" #include "Materials/MaterialExpressionTextureSampleParameterCube.h" #include "Materials/MaterialExpressionTextureSampleParameterSubUV.h" #include "Materials/MaterialExpressionTransformPosition.h" #include "Materials/MaterialExpressionVectorParameter.h" #include "Materials/MaterialExpressionStaticBoolParameter.h" #include "Materials/MaterialFunction.h" #include "Materials/MaterialFunctionInstance.h" #include "Materials/MaterialParameterCollection.h" #include "MaterialEditorActions.h" #include "MaterialExpressionClasses.h" #include "MaterialCompiler.h" #include "EditorSupportDelegates.h" #include "AssetRegistryModule.h" #include "IAssetTools.h" #include "IAssetTypeActions.h" #include "AssetToolsModule.h" #include "SMaterialEditorTitleBar.h" #include "ScopedTransaction.h" #include "BusyCursor.h" #include "PropertyEditorModule.h" #include "MaterialEditorDetailCustomization.h" #include "MaterialInstanceEditor.h" #include "EditorViewportCommands.h" #include "GraphEditor.h" #include "GraphEditorActions.h" #include "Kismet2/BlueprintEditorUtils.h" #include "Logging/TokenizedMessage.h" #include "EdGraphUtilities.h" #include "SNodePanel.h" #include "MaterialEditorUtilities.h" #include "SMaterialPalette.h" #include "FindInMaterial.h" #include "Misc/FeedbackContext.h" #include "UObject/UObjectHash.h" #include "UObject/UObjectIterator.h" #include "Widgets/Colors/SColorPicker.h" #include "EditorClassUtils.h" #include "IDocumentation.h" #include "Widgets/Docking/SDockTab.h" #include "Developer/MessageLog/Public/IMessageLogListing.h" #include "Developer/MessageLog/Public/MessageLogInitializationOptions.h" #include "Developer/MessageLog/Public/MessageLogModule.h" #include "Framework/Commands/GenericCommands.h" #include "CanvasTypes.h" #include "Engine/Selection.h" #include "Materials/Material.h" #include "AdvancedPreviewSceneModule.h" #include "MaterialLayersFunctionsCustomization.h" #include "MaterialEditor/MaterialEditorPreviewParameters.h" #include "SMaterialLayersFunctionsTree.h" #include "Materials/MaterialExpressionSetMaterialAttributes.h" #include "Settings/EditorExperimentalSettings.h" #include "Materials/MaterialExpressionBlendMaterialAttributes.h" #include "Materials/MaterialExpressionMaterialLayerOutput.h" #include "MaterialStats.h" #include "SMaterialParametersOverviewWidget.h" #include "IPropertyRowGenerator.h" #define LOCTEXT_NAMESPACE "MaterialEditor" DEFINE_LOG_CATEGORY_STATIC(LogMaterialEditor, Log, All); static TAutoConsoleVariable CVarMaterialEdUseDevShaders( TEXT("r.MaterialEditor.UseDevShaders"), 1, TEXT("Toggles whether the material editor will use shaders that include extra overhead incurred by the editor. Material editor must be re-opened if changed at runtime."), ECVF_RenderThreadSafe); const FName FMaterialEditor::PreviewTabId( TEXT( "MaterialEditor_Preview" ) ); const FName FMaterialEditor::GraphCanvasTabId( TEXT( "MaterialEditor_GraphCanvas" ) ); const FName FMaterialEditor::PropertiesTabId( TEXT( "MaterialEditor_MaterialProperties" ) ); const FName FMaterialEditor::PaletteTabId( TEXT( "MaterialEditor_Palette" ) ); const FName FMaterialEditor::FindTabId( TEXT( "MaterialEditor_Find" ) ); const FName FMaterialEditor::PreviewSettingsTabId( TEXT ("MaterialEditor_PreviewSettings" ) ); const FName FMaterialEditor::ParameterDefaultsTabId( TEXT ("MaterialEditor_ParameterDefaults" ) ); const FName FMaterialEditor::LayerPropertiesTabId(TEXT("MaterialInstanceEditor_MaterialLayerProperties")); /////////////////////////// // FMatExpressionPreview // /////////////////////////// bool FMatExpressionPreview::ShouldCache(EShaderPlatform Platform, const FShaderType* ShaderType, const FVertexFactoryType* VertexFactoryType) const { if(VertexFactoryType == FindVertexFactoryType(FName(TEXT("FLocalVertexFactory"), FNAME_Find))) { // we only need the non-light-mapped, base pass, local vertex factory shaders for drawing an opaque Material Tile // @todo: Added a FindShaderType by fname or something" if (IsMobilePlatform(Platform)) { if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassForForwardShadingVSFNoLightMapPolicy")) || FCString::Stristr(ShaderType->GetName(), TEXT("BasePassForForwardShadingPSFNoLightMapPolicy"))) { return true; } } else { if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassVSFNoLightMapPolicy")) || FCString::Stristr(ShaderType->GetName(), TEXT("BasePassHSFNoLightMapPolicy")) || FCString::Stristr(ShaderType->GetName(), TEXT("BasePassDSFNoLightMapPolicy"))) { return true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("BasePassPSFNoLightMapPolicy"))) { return true; } else if (FCString::Stristr(ShaderType->GetName(), TEXT("Simple"))) { return true; } } } return false; } int32 FMatExpressionPreview::CompilePropertyAndSetMaterialProperty(EMaterialProperty Property, FMaterialCompiler* Compiler, EShaderFrequency OverrideShaderFrequency, bool bUsePreviousFrameTime) const { // needs to be called in this function!! Compiler->SetMaterialProperty(Property, OverrideShaderFrequency, bUsePreviousFrameTime); int32 Ret = INDEX_NONE; if( Property == MP_EmissiveColor && Expression.IsValid()) { // Hardcoding output 0 as we don't have the UI to specify any other output const int32 OutputIndex = 0; // Get back into gamma corrected space, as DrawTile does not do this adjustment. Ret = Compiler->Power(Compiler->Max(Expression->CompilePreview(Compiler, OutputIndex), Compiler->Constant(0)), Compiler->Constant(1.f / 2.2f)); } else if (Property == MP_WorldPositionOffset) { //set to 0 to prevent off by 1 pixel errors Ret = Compiler->Constant(0.0f); } else if (Property >= MP_CustomizedUVs0 && Property <= MP_CustomizedUVs7) { const int32 TextureCoordinateIndex = Property - MP_CustomizedUVs0; Ret = Compiler->TextureCoordinate(TextureCoordinateIndex, false, false); } else if (Property == MP_ShadingModel) { FMaterialShadingModelField ShadingModels = Compiler->GetMaterialShadingModels(); Ret = Compiler->ShadingModel(ShadingModels.GetFirstShadingModel()); } else { Ret = Compiler->Constant(1.0f); } // output should always be the right type for this property return Compiler->ForceCast(Ret, FMaterialAttributeDefinitionMap::GetValueType(Property)); } void FMatExpressionPreview::NotifyCompilationFinished() { if (Expression.IsValid() && Expression->GraphNode) { CastChecked(Expression->GraphNode)->bPreviewNeedsUpdate = true; } } ///////////////////// // FMaterialEditor // ///////////////////// void FMaterialEditor::RegisterTabSpawners(const TSharedRef& InTabManager) { WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_MaterialEditor", "Material Editor")); auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef(); FAssetEditorToolkit::RegisterTabSpawners(InTabManager); InTabManager->RegisterTabSpawner( PreviewTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Preview) ) .SetDisplayName( LOCTEXT("ViewportTab", "Viewport") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Viewports")); InTabManager->RegisterTabSpawner( GraphCanvasTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_GraphCanvas) ) .SetDisplayName( LOCTEXT("GraphCanvasTab", "Graph") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x")); InTabManager->RegisterTabSpawner( PropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_MaterialProperties) ) .SetDisplayName( LOCTEXT("DetailsTab", "Details") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); InTabManager->RegisterTabSpawner( PaletteTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Palette) ) .SetDisplayName( LOCTEXT("PaletteTab", "Palette") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.Palette")); InTabManager->RegisterTabSpawner(FindTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Find)) .SetDisplayName(LOCTEXT("FindTab", "Find Results")) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "Kismet.Tabs.FindResults")); InTabManager->RegisterTabSpawner(PreviewSettingsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_PreviewSettings)) .SetDisplayName( LOCTEXT("PreviewSceneSettingsTab", "Preview Scene Settings") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); InTabManager->RegisterTabSpawner(ParameterDefaultsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_ParameterDefaults)) .SetDisplayName(LOCTEXT("ParameterDefaultsTab", "Parameter Defaults")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked("MaterialEditor"); if (MaterialEditorModule->MaterialLayersEnabled()) { InTabManager->RegisterTabSpawner(LayerPropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_LayerProperties)) .SetDisplayName(LOCTEXT("LayerPropertiesTab", "Layer Parameters")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Layers")); } MaterialStatsManager->RegisterTabs(); OnRegisterTabSpawners().Broadcast(InTabManager); } void FMaterialEditor::UnregisterTabSpawners(const TSharedRef& InTabManager) { FAssetEditorToolkit::UnregisterTabSpawners(InTabManager); InTabManager->UnregisterTabSpawner( PreviewTabId ); InTabManager->UnregisterTabSpawner( GraphCanvasTabId ); InTabManager->UnregisterTabSpawner( PropertiesTabId ); InTabManager->UnregisterTabSpawner( PaletteTabId ); InTabManager->UnregisterTabSpawner( FindTabId ); InTabManager->UnregisterTabSpawner( PreviewSettingsTabId ); InTabManager->UnregisterTabSpawner( ParameterDefaultsTabId ); InTabManager->UnregisterTabSpawner( LayerPropertiesTabId ); MaterialStatsManager->UnregisterTabs(); OnUnregisterTabSpawners().Broadcast(InTabManager); } void FMaterialEditor::InitEditorForMaterial(UMaterial* InMaterial) { check(InMaterial); OriginalMaterial = InMaterial; MaterialFunction = NULL; OriginalMaterialObject = InMaterial; ExpressionPreviewMaterial = NULL; // Create a copy of the material for preview usage (duplicating to a different class than original!) // Propagate all object flags except for RF_Standalone, otherwise the preview material won't GC once // the material editor releases the reference. Material = (UMaterial*)StaticDuplicateObject(OriginalMaterial, GetTransientPackage(), NAME_None, ~RF_Standalone, UPreviewMaterial::StaticClass()); Material->CancelOutstandingCompilation(); //The material is compiled later on anyway so no need to do it in Duplication/PostLoad. //I'm hackily canceling the jobs here but we should really not add the jobs in the first place. <<--- TODO Material->bAllowDevelopmentShaderCompile = CVarMaterialEdUseDevShaders.GetValueOnGameThread(); // Remove NULL entries, so the rest of the material editor can assume all entries of Material->Expressions are valid // This can happen if an expression class was removed for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { if (!Material->Expressions[ExpressionIndex]) { Material->Expressions.RemoveAt(ExpressionIndex); } } TArray Groups; GetAllMaterialExpressionGroups(&Groups); } void FMaterialEditor::InitEditorForMaterialFunction(UMaterialFunction* InMaterialFunction) { check(InMaterialFunction); Material = NULL; MaterialFunction = InMaterialFunction; OriginalMaterialObject = InMaterialFunction; ExpressionPreviewMaterial = NULL; // Create a temporary material to preview the material function Material = NewObject(); { FArchiveUObject DummyArchive; // Hack: serialize the new material with an archive that does nothing so that its material resources are created Material->Serialize(DummyArchive); } Material->SetShadingModel(MSM_Unlit); // Propagate all object flags except for RF_Standalone, otherwise the preview material function won't GC once // the material editor releases the reference. MaterialFunction = (UMaterialFunction*)StaticDuplicateObject(InMaterialFunction, GetTransientPackage(), NAME_None, ~RF_Standalone, UMaterialFunction::StaticClass()); MaterialFunction->ParentFunction = InMaterialFunction; OriginalMaterial = Material; TArray Groups; GetAllMaterialExpressionGroups(&Groups); } void FMaterialEditor::InitMaterialEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UObject* ObjectToEdit ) { EditorOptions = NULL; bMaterialDirty = false; bStatsFromPreviewMaterial = false; ColorPickerObject = NULL; // Support undo/redo Material->SetFlags(RF_Transactional); GEditor->RegisterForUndo(this); MaterialStatsManager = FMaterialStatsUtils::CreateMaterialStats(this); MaterialStatsManager->SetMaterialDisplayName(OriginalMaterial->GetName()); if (!Material->MaterialGraph) { Material->MaterialGraph = CastChecked(FBlueprintEditorUtils::CreateNewGraph(Material, NAME_None, UMaterialGraph::StaticClass(), UMaterialGraphSchema::StaticClass())); } Material->MaterialGraph->Material = Material; Material->MaterialGraph->MaterialFunction = MaterialFunction; Material->MaterialGraph->RealtimeDelegate.BindSP(this, &FMaterialEditor::IsToggleRealTimeExpressionsChecked); Material->MaterialGraph->MaterialDirtyDelegate.BindSP(this, &FMaterialEditor::SetMaterialDirty); Material->MaterialGraph->ToggleCollapsedDelegate.BindSP(this, &FMaterialEditor::ToggleCollapsed); // copy material usage for( int32 Usage=0; Usage < MATUSAGE_MAX; Usage++ ) { const EMaterialUsage UsageEnum = (EMaterialUsage)Usage; if( OriginalMaterial->GetUsageByFlag(UsageEnum) ) { bool bNeedsRecompile=false; Material->SetMaterialUsage(bNeedsRecompile,UsageEnum); } } // Manually copy bUsedAsSpecialEngineMaterial as it is duplicate transient to prevent accidental creation of new special engine materials Material->bUsedAsSpecialEngineMaterial = OriginalMaterial->bUsedAsSpecialEngineMaterial; NodeQualityLevel = EMaterialQualityLevel::Num; NodeFeatureLevel = ERHIFeatureLevel::Num; bPreviewFeaturesChanged = true; // Register our commands. This will only register them if not previously registered FGraphEditorCommands::Register(); FMaterialEditorCommands::Register(); FMaterialEditorSpawnNodeCommands::Register(); FEditorSupportDelegates::MaterialUsageFlagsChanged.AddRaw(this, &FMaterialEditor::OnMaterialUsageFlagsChanged); FEditorSupportDelegates::VectorParameterDefaultChanged.AddRaw(this, &FMaterialEditor::OnVectorParameterDefaultChanged); FEditorSupportDelegates::ScalarParameterDefaultChanged.AddRaw(this, &FMaterialEditor::OnScalarParameterDefaultChanged); FAssetRegistryModule& AssetRegistryModule = FModuleManager::GetModuleChecked(TEXT("AssetRegistry")); AssetRegistryModule.Get().OnAssetRenamed().AddSP( this, &FMaterialEditor::RenameAssetFromRegistry ); CreateInternalWidgets(); // Do setup previously done in SMaterialEditorCanvas SetPreviewMaterial(Material); Material->bIsPreviewMaterial = true; FMaterialEditorUtilities::InitExpressions(Material); UpdatePreviewViewportsVisibility(); BindCommands(); const TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_MaterialEditor_Layout_v10") ->AddArea ( FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.1f) ->SetHideTabWell( true ) ->AddTab(GetToolbarTabId(), ETabState::OpenedTab) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->SetSizeCoefficient(0.9f) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Vertical) ->SetSizeCoefficient(0.2f) ->Split ( FTabManager::NewStack() ->SetHideTabWell( true ) ->AddTab( PreviewTabId, ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack() ->AddTab( PropertiesTabId, ETabState::OpenedTab ) ->AddTab( PreviewSettingsTabId, ETabState::ClosedTab ) ->AddTab( ParameterDefaultsTabId, ETabState::OpenedTab ) ->AddTab( LayerPropertiesTabId, ETabState::ClosedTab ) ->SetForegroundTab( PropertiesTabId ) ) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation( Orient_Vertical ) ->SetSizeCoefficient(0.80f) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.8f) ->SetHideTabWell( true ) ->AddTab( GraphCanvasTabId, ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient( 0.20f ) ->AddTab(FMaterialStats::GetGridStatsTabName(), ETabState::ClosedTab) ->AddTab(FMaterialStats::GetGridOldStatsTabName(), ETabState::ClosedTab) ->AddTab( FindTabId, ETabState::ClosedTab ) ) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->SetSizeCoefficient(0.2f) ->Split ( FTabManager::NewStack() ->AddTab( PaletteTabId, ETabState::OpenedTab ) ) ) ) ); const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; // Add the preview material to the objects being edited, so that we can find this editor from the temporary material graph TArray< UObject* > ObjectsToEdit; ObjectsToEdit.Add(ObjectToEdit); ObjectsToEdit.Add(Material); FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, MaterialEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsToEdit, false ); AddMenuExtender(GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked( "MaterialEditor" ); AddMenuExtender(MaterialEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); ExtendToolbar(); RegenerateMenusAndToolbars(); // @todo toolkit world centric editing /*if( IsWorldCentricAssetEditor() ) { SpawnToolkitTab(GetToolbarTabId(), FString(), EToolkitTabSpot::ToolBar); SpawnToolkitTab(PreviewTabId, FString(), EToolkitTabSpot::Viewport); SpawnToolkitTab(GraphCanvasTabId, FString(), EToolkitTabSpot::Document); SpawnToolkitTab(PropertiesTabId, FString(), EToolkitTabSpot::Details); }*/ // Load editor settings from disk. LoadEditorSettings(); // Set the preview mesh for the material. This call must occur after the toolbar is initialized. if (!SetPreviewAssetByName(*Material->PreviewMesh.ToString())) { // The material preview mesh couldn't be found or isn't loaded. Default to the one of the primitive types. SetPreviewAsset( GUnrealEd->GetThumbnailManager()->EditorSphere ); } // Initialize expression previews. if (MaterialFunction) { // Support undo/redo for the material function if it exists MaterialFunction->SetFlags(RF_Transactional); Material->Expressions = MaterialFunction->FunctionExpressions; Material->EditorComments = MaterialFunction->FunctionEditorComments; // Remove NULL entries, so the rest of the material editor can assume all entries of Material->Expressions are valid // This can happen if an expression class was removed for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { if (!Material->Expressions[ExpressionIndex]) { Material->Expressions.RemoveAt(ExpressionIndex); } } if (Material->Expressions.Num() == 0) { // If this is an empty function, create an output by default and start previewing it if (GraphEditor.IsValid()) { check(!bMaterialDirty); FVector2D OutputPlacement = FVector2D(200, 300); if (GetDefault()->bExampleLayersAndBlends) { switch (MaterialFunction->GetMaterialFunctionUsage()) { case(EMaterialFunctionUsage::MaterialLayer): { OutputPlacement = FVector2D(300, 269); break; } case(EMaterialFunctionUsage::MaterialLayerBlend): { OutputPlacement = FVector2D(275, 269); break; } default: break; } } UMaterialExpression* Expression; if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::Default) { Expression = CreateNewMaterialExpression(UMaterialExpressionFunctionOutput::StaticClass(), OutputPlacement, false, true); SetPreviewExpression(Expression); // This shouldn't count as having dirtied the material, so reset the flag bMaterialDirty = false; } else { Expression = CreateNewMaterialExpression(UMaterialExpressionMaterialLayerOutput::StaticClass(), OutputPlacement, false, true); SetPreviewExpression(Expression); // This shouldn't count as having dirtied the material, so reset the flag bMaterialDirty = false; } // We can check the usage here and add the appropriate inputs too (e.g. Layer==1MA, Blend==2MA) if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayer) { UMaterialExpression* Input = CreateNewMaterialExpression(UMaterialExpressionFunctionInput::StaticClass(), FVector2D(-350, 300), false, true); if (Input) { UMaterialExpressionFunctionInput* BaseAttributesInput = Cast(Input); BaseAttributesInput->InputType = FunctionInput_MaterialAttributes; BaseAttributesInput->InputName = TEXT("Material Attributes"); BaseAttributesInput->bUsePreviewValueAsDefault = true; } if (GetDefault()->bExampleLayersAndBlends) { UMaterialExpression* SetMaterialAttributes = CreateNewMaterialExpression(UMaterialExpressionSetMaterialAttributes::StaticClass(), FVector2D(40, 300), false, true); if (Input && SetMaterialAttributes) { UMaterialEditingLibrary::ConnectMaterialExpressions(Input, FString(), SetMaterialAttributes, FString()); UMaterialEditingLibrary::ConnectMaterialExpressions(SetMaterialAttributes, FString(), Expression, FString()); bMaterialDirty = true; } } } else if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayerBlend) { // "Top layer" should be below "bottom layer" on the graph, to align with B on blend nodes UMaterialExpression* InputTop = CreateNewMaterialExpression(UMaterialExpressionFunctionInput::StaticClass(), FVector2D(-300, 400), false, true); if (InputTop) { UMaterialExpressionFunctionInput* BaseAttributesInput = Cast(InputTop); BaseAttributesInput->InputType = FunctionInput_MaterialAttributes; BaseAttributesInput->InputName = TEXT("Top Layer"); BaseAttributesInput->bUsePreviewValueAsDefault = true; } UMaterialExpression* InputBottom = CreateNewMaterialExpression(UMaterialExpressionFunctionInput::StaticClass(), FVector2D(-300, 200), false, true); if (InputBottom) { UMaterialExpressionFunctionInput* BaseAttributesInput = Cast(InputBottom); BaseAttributesInput->InputType = FunctionInput_MaterialAttributes; BaseAttributesInput->InputName = TEXT("Bottom Layer"); BaseAttributesInput->bUsePreviewValueAsDefault = true; } if (GetDefault()->bExampleLayersAndBlends) { UMaterialExpression* BlendMaterialAttributes = CreateNewMaterialExpression(UMaterialExpressionBlendMaterialAttributes::StaticClass(), FVector2D(40, 300), false, true); if (InputTop && InputBottom && BlendMaterialAttributes) { UMaterialEditingLibrary::ConnectMaterialExpressions(InputBottom, FString(), BlendMaterialAttributes, FString(TEXT("A"))); UMaterialEditingLibrary::ConnectMaterialExpressions(InputTop, FString(), BlendMaterialAttributes, FString(TEXT("B"))); UMaterialEditingLibrary::ConnectMaterialExpressions(BlendMaterialAttributes, FString(), Expression, FString()); bMaterialDirty = true; } } } } } else { bool bSetPreviewExpression = false; UMaterialExpressionFunctionOutput* FirstOutput = NULL; for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { UMaterialExpression* Expression = Material->Expressions[ExpressionIndex]; // Setup the expression to be used with the preview material instead of the function Expression->Function = NULL; Expression->Material = Material; UMaterialExpressionFunctionOutput* FunctionOutput = Cast(Expression); if (FunctionOutput) { FirstOutput = FunctionOutput; if (FunctionOutput->bLastPreviewed) { bSetPreviewExpression = true; // Preview the last output previewed SetPreviewExpression(FunctionOutput); } } } if (!bSetPreviewExpression && FirstOutput) { SetPreviewExpression(FirstOutput); } } } // Store the name of this material (for the tutorial widget meta) if (OriginalMaterial != nullptr) { Material->MaterialGraph->OriginalMaterialFullName = OriginalMaterial->GetName(); } Material->MaterialGraph->RebuildGraph(); RecenterEditor(); //Make sure the preview material is initialized. UpdatePreviewMaterial(true); RegenerateCodeView(true); ForceRefreshExpressionPreviews(); if (OriginalMaterial->bUsedAsSpecialEngineMaterial) { FSuppressableWarningDialog::FSetupInfo Info( NSLOCTEXT("UnrealEd", "Warning_EditingDefaultMaterial", "Editing this Default Material is an advanced workflow.\nDefault Materials must be available as a code fallback at all times, compilation errors are not handled gracefully. Save your work."), NSLOCTEXT("UnrealEd", "Warning_EditingDefaultMaterial_Title", "Warning: Editing Default Material" ), "Warning_EditingDefaultMaterial"); Info.ConfirmText = NSLOCTEXT("ModalDialogs", "EditingDefaultMaterialOk", "Ok"); FSuppressableWarningDialog EditingDefaultMaterial( Info ); EditingDefaultMaterial.ShowModal(); } if (GetDefault()->bExampleLayersAndBlends && bMaterialDirty) { SaveAsset_Execute(); } } FMaterialEditor::FMaterialEditor() : bMaterialDirty(false) , bStatsFromPreviewMaterial(false) , Material(NULL) , OriginalMaterial(NULL) , ExpressionPreviewMaterial(NULL) , EmptyMaterial(NULL) , PreviewExpression(NULL) , MaterialFunction(NULL) , OriginalMaterialObject(NULL) , EditorOptions(NULL) , ScopedTransaction(NULL) , bAlwaysRefreshAllPreviews(false) , bHideUnusedConnectors(false) , bLivePreview(true) , bIsRealtime(false) , bShowBuiltinStats(false) , MenuExtensibilityManager(new FExtensibilityManager) , ToolBarExtensibilityManager(new FExtensibilityManager) , MaterialEditorInstance(NULL) { } FMaterialEditor::~FMaterialEditor() { // Broadcast that this editor is going down to all listeners OnMaterialEditorClosed().Broadcast(); for (int32 ParameterIndex = 0; ParameterIndex < OverriddenVectorParametersToRevert.Num(); ParameterIndex++) { SetVectorParameterDefaultOnDependentMaterials(OverriddenVectorParametersToRevert[ParameterIndex], FLinearColor::Black, false); } for (int32 ParameterIndex = 0; ParameterIndex < OverriddenScalarParametersToRevert.Num(); ParameterIndex++) { SetScalarParameterDefaultOnDependentMaterials(OverriddenScalarParametersToRevert[ParameterIndex], 0, false); } // Unregister this delegate FEditorSupportDelegates::MaterialUsageFlagsChanged.RemoveAll(this); FEditorSupportDelegates::VectorParameterDefaultChanged.RemoveAll(this); FEditorSupportDelegates::ScalarParameterDefaultChanged.RemoveAll(this); // Null out the expression preview material so they can be GC'ed ExpressionPreviewMaterial = NULL; // Save editor settings to disk. SaveEditorSettings(); MaterialDetailsView.Reset(); { SCOPED_SUSPEND_RENDERING_THREAD(true); ExpressionPreviews.Empty(); } check( !ScopedTransaction ); GEditor->UnregisterForUndo( this ); MaterialEditorInstance = NULL; } void FMaterialEditor::GetAllMaterialExpressionGroups(TArray* OutGroups) { TArray UpdatedGroups; for (int32 MaterialExpressionIndex = 0; MaterialExpressionIndex < Material->Expressions.Num(); ++MaterialExpressionIndex) { UMaterialExpression* MaterialExpression = Material->Expressions[ MaterialExpressionIndex ]; UMaterialExpressionParameter* Param = Cast(MaterialExpression); UMaterialExpressionTextureSampleParameter* TextureS = Cast(MaterialExpression); UMaterialExpressionFontSampleParameter* FontS = Cast(MaterialExpression); if (Param) { const FString& GroupName = Param->Group.ToString(); OutGroups->AddUnique(GroupName); if (Material->AttemptInsertNewGroupName(GroupName)) { UpdatedGroups.Add(FParameterGroupData(GroupName, 0)); } else { FParameterGroupData* ParameterGroupDataElement = Material->ParameterGroupData.FindByPredicate([&GroupName](const FParameterGroupData& DataElement) { return GroupName == DataElement.GroupName; }); UpdatedGroups.Add(FParameterGroupData(GroupName, ParameterGroupDataElement->GroupSortPriority)); } } else if (TextureS) { const FString& GroupName = TextureS->Group.ToString(); OutGroups->AddUnique(GroupName); if (Material->AttemptInsertNewGroupName(GroupName)) { UpdatedGroups.Add(FParameterGroupData(GroupName, 0)); } else { FParameterGroupData* ParameterGroupDataElement = Material->ParameterGroupData.FindByPredicate([&GroupName](const FParameterGroupData& DataElement) { return GroupName == DataElement.GroupName; }); UpdatedGroups.Add(FParameterGroupData(GroupName, ParameterGroupDataElement->GroupSortPriority)); } } else if (FontS) { const FString& GroupName = FontS->Group.ToString(); OutGroups->AddUnique(GroupName); if (Material->AttemptInsertNewGroupName(GroupName)) { UpdatedGroups.Add(FParameterGroupData(GroupName, 0)); } else { FParameterGroupData* ParameterGroupDataElement = Material->ParameterGroupData.FindByPredicate([&GroupName](const FParameterGroupData& DataElement) { return GroupName == DataElement.GroupName; }); UpdatedGroups.Add(FParameterGroupData(GroupName, ParameterGroupDataElement->GroupSortPriority)); } } } Material->ParameterGroupData = UpdatedGroups; } void FMaterialEditor::UpdatePreviewViewportsVisibility() { if( Material->IsUIMaterial() ) { PreviewViewport->SetVisibility(EVisibility::Collapsed); PreviewUIViewport->SetVisibility(EVisibility::Visible); } else { PreviewViewport->SetVisibility(EVisibility::Visible); PreviewUIViewport->SetVisibility(EVisibility::Collapsed); } } void FMaterialEditor::SetQualityPreview(EMaterialQualityLevel::Type NewQuality) { NodeQualityLevel = NewQuality; bPreviewFeaturesChanged = true; } bool FMaterialEditor::IsQualityPreviewChecked(EMaterialQualityLevel::Type TestQuality) { return NodeQualityLevel == TestQuality; } void FMaterialEditor::SetFeaturePreview(ERHIFeatureLevel::Type NewFeatureLevel) { NodeFeatureLevel = NewFeatureLevel; bPreviewFeaturesChanged = true; } bool FMaterialEditor::IsFeaturePreviewChecked(ERHIFeatureLevel::Type TestFeatureLevel) { return NodeFeatureLevel == TestFeatureLevel; } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FMaterialEditor::CreateInternalWidgets() { PreviewViewport = SNew(SMaterialEditor3DPreviewViewport) .MaterialEditor(SharedThis(this)); PreviewUIViewport = SNew(SMaterialEditorUIPreviewViewport, Material); FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked( "PropertyEditor" ); GraphEditor = CreateGraphEditorWidget(); // Manually set zoom level to avoid deferred zooming GraphEditor->SetViewLocation(FVector2D::ZeroVector, 1); const FDetailsViewArgs DetailsViewArgs( false, false, true, FDetailsViewArgs::HideNameArea, true, this ); MaterialDetailsView = PropertyEditorModule.CreateDetailView( DetailsViewArgs ); FOnGetDetailCustomizationInstance LayoutExpressionParameterDetails = FOnGetDetailCustomizationInstance::CreateStatic( &FMaterialExpressionParameterDetails::MakeInstance, FOnCollectParameterGroups::CreateSP(this, &FMaterialEditor::GetAllMaterialExpressionGroups) ); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionParameter::StaticClass(), LayoutExpressionParameterDetails ); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionFontSampleParameter::StaticClass(), LayoutExpressionParameterDetails ); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionTextureSampleParameter::StaticClass(), LayoutExpressionParameterDetails ); FOnGetDetailCustomizationInstance LayoutLayerExpressionParameterDetails = FOnGetDetailCustomizationInstance::CreateStatic( &FMaterialExpressionLayersParameterDetails::MakeInstance, FOnCollectParameterGroups::CreateSP(this, &FMaterialEditor::GetAllMaterialExpressionGroups)); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionMaterialAttributeLayers::StaticClass(), LayoutLayerExpressionParameterDetails ); FOnGetDetailCustomizationInstance LayoutCollectionParameterDetails = FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialExpressionCollectionParameterDetails::MakeInstance); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionCollectionParameter::StaticClass(), LayoutCollectionParameterDetails ); MaterialDetailsView->OnFinishedChangingProperties().AddSP(this, &FMaterialEditor::OnFinishedChangingProperties); PropertyEditorModule.RegisterCustomClassLayout( UMaterial::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialDetailCustomization::MakeInstance ) ); MaterialEditorInstance = NewObject(GetTransientPackage(), NAME_None, RF_Transactional); MaterialEditorInstance->PreviewMaterial = Material; MaterialEditorInstance->OriginalMaterial = OriginalMaterial; if (MaterialFunction) { MaterialEditorInstance->OriginalFunction = MaterialFunction->ParentFunction; } MaterialParametersOverviewWidget = SNew(SMaterialParametersOverviewPanel) .InMaterialEditorInstance(MaterialEditorInstance); MaterialParametersOverviewWidget->GetGenerator()->OnFinishedChangingProperties().AddSP(this, &FMaterialEditor::OnFinishedChangingParametersFromOverview); IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked("MaterialEditor"); if (MaterialEditorModule->MaterialLayersEnabled()) { MaterialLayersFunctionsInstance = SNew(SMaterialLayersFunctionsMaterialWrapper) .InMaterialEditorInstance(MaterialEditorInstance); } Palette = SNew(SMaterialPalette, SharedThis(this)); FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked("MessageLog"); FMessageLogInitializationOptions LogOptions; // Show Pages so that user is never allowed to clear log messages LogOptions.bShowPages = false; LogOptions.bShowFilters = false; //TODO - Provide custom filters? E.g. "Critical Errors" vs "Errors" needed for materials? LogOptions.bAllowClear = false; LogOptions.MaxPageCount = 1; StatsListing = MessageLogModule.CreateLogListing( "MaterialEditorStats", LogOptions ); Stats = MessageLogModule.CreateLogListingWidget( StatsListing.ToSharedRef() ); FindResults = SNew(SFindInMaterial, SharedThis(this)); RegenerateCodeView(); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void FMaterialEditor::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) { bool bRefreshNodePreviews = false; if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { UStructProperty* Property = Cast(PropertyChangedEvent.Property); if (Property != nullptr) { if (Property->Struct->GetFName() == TEXT("LinearColor") || Property->Struct->GetFName() == TEXT("Color")) // if we changed a color property refresh the previews { bRefreshNodePreviews = true; } } // If we changed any of the parameter values from the Parameter Defaults panel if (PropertyChangedEvent.Property->GetName() == TEXT("Parameters")) { bRefreshNodePreviews = true; } if (bRefreshNodePreviews) { RefreshExpressionPreviews(true); } RefreshPreviewViewport(); UpdatePreviewMaterial(); MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance); } } void FMaterialEditor::OnFinishedChangingParametersFromOverview(const FPropertyChangedEvent& PropertyChangedEvent) { bool bRefreshNodePreviews = false; if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { RefreshExpressionPreviews(true); RefreshPreviewViewport(); UpdatePreviewMaterial(); TArray> SelectedObjects = MaterialDetailsView->GetSelectedObjects(); MaterialDetailsView->SetObjects(SelectedObjects, true); Material->MarkPackageDirty(); SetMaterialDirty(); } } FName FMaterialEditor::GetToolkitFName() const { return FName("MaterialEditor"); } FText FMaterialEditor::GetBaseToolkitName() const { return LOCTEXT("AppLabel", "Material Editor"); } FText FMaterialEditor::GetToolkitName() const { const UObject* EditingObject = GetEditingObjects()[0]; check(EditingObject); return FText::FromString(EditingObject->GetName()); } FText FMaterialEditor::GetToolkitToolTipText() const { const UObject* EditingObject = GetEditingObjects()[0]; // Overridden to accommodate editing of multiple objects (original and preview materials) return FAssetEditorToolkit::GetToolTipTextForObject(EditingObject); } FString FMaterialEditor::GetWorldCentricTabPrefix() const { return LOCTEXT("WorldCentricTabPrefix", "Material ").ToString(); } FLinearColor FMaterialEditor::GetWorldCentricTabColorScale() const { return FLinearColor( 0.3f, 0.2f, 0.5f, 0.5f ); } void FMaterialEditor::Tick( float InDeltaTime ) { UpdateMaterialInfoList(); UpdateMaterialinfoList_Old(); UpdateGraphNodeStates(); } TStatId FMaterialEditor::GetStatId() const { RETURN_QUICK_DECLARE_CYCLE_STAT(FMaterialEditor, STATGROUP_Tickables); } void FMaterialEditor::UpdateThumbnailInfoPreviewMesh(UMaterialInterface* MatInterface) { if ( MatInterface ) { FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); TWeakPtr AssetTypeActions = AssetToolsModule.Get().GetAssetTypeActionsForClass( MatInterface->GetClass() ); if ( AssetTypeActions.IsValid() ) { USceneThumbnailInfoWithPrimitive* OriginalThumbnailInfo = Cast(AssetTypeActions.Pin()->GetThumbnailInfo(MatInterface)); if ( OriginalThumbnailInfo ) { OriginalThumbnailInfo->PreviewMesh = MatInterface->PreviewMesh; MatInterface->PostEditChange(); } } } } void FMaterialEditor::ExtendToolbar() { TSharedPtr ToolbarExtender = MakeShareable(new FExtender); ToolbarExtender->AddToolBarExtension( "Asset", EExtensionHook::After, GetToolkitCommands(), FToolBarExtensionDelegate::CreateSP( this, &FMaterialEditor::FillToolbar ) ); AddToolbarExtender(ToolbarExtender); AddToolbarExtender(GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked( "MaterialEditor" ); AddToolbarExtender(MaterialEditorModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); } void FMaterialEditor::FillToolbar(FToolBarBuilder& ToolbarBuilder) { ToolbarBuilder.BeginSection("Apply"); { ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().Apply); } ToolbarBuilder.EndSection(); ToolbarBuilder.BeginSection("Search"); { ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().FindInMaterial); } ToolbarBuilder.EndSection(); ToolbarBuilder.BeginSection("Graph"); { ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().CameraHome); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().CleanUnusedExpressions); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ShowHideConnectors); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleLivePreview); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleRealtimeExpressions); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().AlwaysRefreshAllPreviews); } ToolbarBuilder.EndSection(); ToolbarBuilder.AddSeparator(); ToolbarBuilder.BeginSection("Stats"); { ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().ToggleMaterialStats); ToolbarBuilder.AddToolBarButton(FMaterialEditorCommands::Get().TogglePlatformStats); ToolbarBuilder.AddComboButton( FUIAction(), FOnGetContent::CreateSP(this, &FMaterialEditor::GeneratePreviewMenuContent), LOCTEXT("NodePreview_Label", "Preview Nodes"), LOCTEXT("NodePreviewToolTip", "Preview the nodes for a given feature level and/or material quality."), FSlateIcon(FEditorStyle::GetStyleSetName(), "FullBlueprintEditor.SwitchToScriptingMode"), false); } ToolbarBuilder.EndSection(); }; TSharedRef< SWidget > FMaterialEditor::GeneratePreviewMenuContent() { bool bShouldCloseWindowAfterMenuSelection = true; FMenuBuilder MenuBuilder(bShouldCloseWindowAfterMenuSelection, GetToolkitCommands()); MenuBuilder.BeginSection("MaterialEditorQualityPreview", LOCTEXT("MaterialQualityHeading", "Quality Level")); { MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_All); MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_High); MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Medium); MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Low); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("MaterialEditorFeaturePreview", LOCTEXT("MaterialFeatureHeading", "Feature Level")); { MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_All); MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_ES2); MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_ES31); MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM4); MenuBuilder.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM5); } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } UMaterialInterface* FMaterialEditor::GetMaterialInterface() const { return Material; } bool FMaterialEditor::ApproveSetPreviewAsset(UObject* InAsset) { bool bApproved = true; // Only permit the use of a skeletal mesh if the material has bUsedWithSkeltalMesh. if (USkeletalMesh* SkeletalMesh = Cast(InAsset)) { if (!Material->bUsedWithSkeletalMesh) { FMessageDialog::Open(EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "Error_MaterialEditor_CantPreviewOnSkelMesh", "Can't preview on the specified skeletal mesh because the material has not been compiled with bUsedWithSkeletalMesh.")); bApproved = false; } } return bApproved; } void FMaterialEditor::GetSaveableObjects(TArray& OutObjects) const { if ((MaterialFunction != nullptr) && MaterialFunction->ParentFunction) { OutObjects.Add(MaterialFunction->ParentFunction); } else { OutObjects.Add(OriginalMaterial); } } void FMaterialEditor::SaveAsset_Execute() { UE_LOG(LogMaterialEditor, Log, TEXT("Saving and Compiling material %s"), *GetEditingObjects()[0]->GetName()); const bool bUpdateSucceeded = (bMaterialDirty ? UpdateOriginalMaterial() : true); if (bUpdateSucceeded) { IMaterialEditor::SaveAsset_Execute(); } } void FMaterialEditor::SaveAssetAs_Execute() { UE_LOG(LogMaterialEditor, Log, TEXT("Saving and Compiling material %s"), *GetEditingObjects()[0]->GetName()); const bool bUpdateSucceeded = (bMaterialDirty ? UpdateOriginalMaterial() : true); if (bUpdateSucceeded) { IMaterialEditor::SaveAssetAs_Execute(); } } bool FMaterialEditor::OnRequestClose() { DestroyColorPicker(); if (bMaterialDirty) { // find out the user wants to do with this dirty material EAppReturnType::Type YesNoCancelReply = FMessageDialog::Open(EAppMsgType::YesNoCancel, FText::Format( NSLOCTEXT("UnrealEd", "Prompt_MaterialEditorClose", "Would you like to apply changes to this material to the original material?\n{0}\n(No will lose all changes!)"), FText::FromString(OriginalMaterialObject->GetPathName()) )); // act on it switch (YesNoCancelReply) { case EAppReturnType::Yes: // update material and exit UpdateOriginalMaterial(); break; case EAppReturnType::No: // exit bMaterialDirty = false; break; case EAppReturnType::Cancel: // don't exit return false; } } return true; } void FMaterialEditor::DrawMaterialInfoStrings( FCanvas* Canvas, const UMaterial* Material, const FMaterialResource* MaterialResource, const TArray& CompileErrors, int32 &DrawPositionY, bool bDrawInstructions, bool bGeneratedNewShaders) { check(Material && MaterialResource); ERHIFeatureLevel::Type FeatureLevel = MaterialResource->GetFeatureLevel(); FString FeatureLevelName; GetFeatureLevelName(FeatureLevel,FeatureLevelName); // The font to use when displaying info strings UFont* FontToUse = GEngine->GetTinyFont(); const int32 SpacingBetweenLines = 13; if (bDrawInstructions) { // Display any errors and messages in the upper left corner of the viewport. TArray Descriptions; FMaterialStatsUtils::GetRepresentativeInstructionCounts(Descriptions, MaterialResource); for (int32 InstructionIndex = 0; InstructionIndex < Descriptions.Num(); InstructionIndex++) { FString InstructionCountString = FString::Printf(TEXT("%s: %u instructions"), *Descriptions[InstructionIndex].ShaderDescription, Descriptions[InstructionIndex].InstructionCount); Canvas->DrawShadowedString(5, DrawPositionY, *InstructionCountString, FontToUse, FLinearColor(1, 1, 0)); DrawPositionY += SpacingBetweenLines; } // Display the number of texture samplers and samplers used by the material. const int32 SamplersUsed = MaterialResource->GetSamplerUsage(); if (SamplersUsed >= 0) { int32 MaxSamplers = GetExpectedFeatureLevelMaxTextureSamplers(MaterialResource->GetFeatureLevel()); Canvas->DrawShadowedString( 5, DrawPositionY, *FString::Printf(TEXT("%s samplers: %u/%u"), FeatureLevel <= ERHIFeatureLevel::ES3_1 ? TEXT("Mobile texture") : TEXT("Texture"), SamplersUsed, MaxSamplers), FontToUse, SamplersUsed > MaxSamplers ? FLinearColor(1,0,0) : FLinearColor(1,1,0) ); DrawPositionY += SpacingBetweenLines; } uint32 NumVSTextureSamples = 0, NumPSTextureSamples = 0; MaterialResource->GetEstimatedNumTextureSamples(NumVSTextureSamples, NumPSTextureSamples); if (NumVSTextureSamples > 0 || NumPSTextureSamples > 0) { Canvas->DrawShadowedString( 5, DrawPositionY, *FString::Printf(TEXT("Texture Lookups (Est.): VS(%u), PS(%u)"), NumVSTextureSamples, NumPSTextureSamples), FontToUse, FLinearColor(1,1,0) ); DrawPositionY += SpacingBetweenLines; } if (bGeneratedNewShaders) { int32 NumShaders = 0; int32 NumPipelines = 0; if(FMaterialShaderMap* ShaderMap = MaterialResource->GetGameThreadShaderMap()) { ShaderMap->CountNumShaders(NumShaders, NumPipelines); } if (NumShaders) { FString ShaderCountString = FString::Printf(TEXT("Num shaders added: %i"), NumShaders); Canvas->DrawShadowedString(5, DrawPositionY, *ShaderCountString, FontToUse, FLinearColor(1, 0.8, 0)); DrawPositionY += SpacingBetweenLines; } } } for(int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++) { Canvas->DrawShadowedString(5, DrawPositionY, *FString::Printf(TEXT("[%s] %s"), *FeatureLevelName, *CompileErrors[ErrorIndex]), FontToUse, FLinearColor(1, 0, 0)); DrawPositionY += SpacingBetweenLines; } } void FMaterialEditor::DrawMessages( FViewport* InViewport, FCanvas* Canvas ) { if( PreviewExpression != NULL ) { Canvas->PushAbsoluteTransform( FTranslationMatrix(FVector(0.0f, 30.0f,0.0f) ) ); // The message to display in the viewport. FString Name = FString::Printf( TEXT("Previewing: %s"), *PreviewExpression->GetName() ); // Size of the tile we are about to draw. Should extend the length of the view in X. const FIntPoint TileSize( InViewport->GetSizeXY().X / Canvas->GetDPIScale(), 25); const FColor PreviewColor( 70,100,200 ); const FColor FontColor( 255,255,128 ); UFont* FontToUse = GEditor->EditorFont; Canvas->DrawTile( 0.0f, 0.0f, TileSize.X, TileSize.Y, 0.0f, 0.0f, 0.0f, 0.0f, PreviewColor ); int32 XL, YL; StringSize( FontToUse, XL, YL, *Name ); if( XL > TileSize.X ) { // There isn't enough room to show the preview expression name Name = TEXT("Previewing"); StringSize( FontToUse, XL, YL, *Name ); } // Center the string in the middle of the tile. const FIntPoint StringPos( (TileSize.X-XL)/2, ((TileSize.Y-YL)/2)+1 ); // Draw the preview message Canvas->DrawShadowedString( StringPos.X, StringPos.Y, *Name, FontToUse, FontColor ); Canvas->PopTransform(); } } void FMaterialEditor::RecenterEditor() { UEdGraphNode* FocusNode = NULL; if (MaterialFunction) { bool bSetPreviewExpression = false; UMaterialExpressionFunctionOutput* FirstOutput = NULL; for (int32 ExpressionIndex = Material->Expressions.Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { UMaterialExpression* Expression = Material->Expressions[ExpressionIndex]; UMaterialExpressionFunctionOutput* FunctionOutput = Cast(Expression); if (FunctionOutput) { FirstOutput = FunctionOutput; if (FunctionOutput->bLastPreviewed) { bSetPreviewExpression = true; FocusNode = FunctionOutput->GraphNode; } } } if (!bSetPreviewExpression && FirstOutput) { FocusNode = FirstOutput->GraphNode; } } else { FocusNode = Material->MaterialGraph->RootNode; } if (FocusNode) { JumpToNode(FocusNode); } else { // Get current view location so that we don't change the zoom amount FVector2D CurrLocation; float CurrZoomLevel; GraphEditor->GetViewLocation(CurrLocation, CurrZoomLevel); GraphEditor->SetViewLocation(FVector2D::ZeroVector, CurrZoomLevel); } } bool FMaterialEditor::SetPreviewAsset(UObject* InAsset) { if (PreviewViewport.IsValid()) { return PreviewViewport->SetPreviewAsset(InAsset); } return false; } bool FMaterialEditor::SetPreviewAssetByName(const TCHAR* InAssetName) { if (PreviewViewport.IsValid()) { return PreviewViewport->SetPreviewAssetByName(InAssetName); } return false; } void FMaterialEditor::SetPreviewMaterial(UMaterialInterface* InMaterialInterface) { if (Material->IsUIMaterial()) { if (PreviewUIViewport.IsValid()) { PreviewUIViewport->SetPreviewMaterial(InMaterialInterface); } } else { if (PreviewViewport.IsValid()) { PreviewViewport->SetPreviewMaterial(InMaterialInterface); } } } void FMaterialEditor::RefreshPreviewViewport() { if (PreviewViewport.IsValid()) { PreviewViewport->RefreshViewport(); } } void FMaterialEditor::LoadEditorSettings() { EditorOptions = NewObject(); if (EditorOptions->bHideUnusedConnectors) {OnShowConnectors();} if (bLivePreview != EditorOptions->bLivePreviewUpdate) { ToggleLivePreview(); } if (EditorOptions->bAlwaysRefreshAllPreviews) {OnAlwaysRefreshAllPreviews();} if (EditorOptions->bRealtimeExpressionViewport) {ToggleRealTimeExpressions();} if ( PreviewViewport.IsValid() ) { if (EditorOptions->bShowGrid) { PreviewViewport->TogglePreviewGrid(); } if (EditorOptions->bRealtimeMaterialViewport) { PreviewViewport->OnToggleRealtime(); } } // Primitive type int32 PrimType; if(GConfig->GetInt(TEXT("MaterialEditor"), TEXT("PrimType"), PrimType, GEditorPerProjectIni)) { PreviewViewport->OnSetPreviewPrimitive((EThumbnailPrimType)PrimType); } } void FMaterialEditor::SaveEditorSettings() { // Save the preview scene check( PreviewViewport.IsValid() ); if ( EditorOptions ) { EditorOptions->bShowGrid = PreviewViewport->IsTogglePreviewGridChecked(); EditorOptions->bRealtimeMaterialViewport = PreviewViewport->IsRealtime(); EditorOptions->bHideUnusedConnectors = !IsOnShowConnectorsChecked(); EditorOptions->bAlwaysRefreshAllPreviews = IsOnAlwaysRefreshAllPreviews(); EditorOptions->bRealtimeExpressionViewport = IsToggleRealTimeExpressionsChecked(); EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked(); EditorOptions->SaveConfig(); } GConfig->SetInt(TEXT("MaterialEditor"), TEXT("PrimType"), PreviewViewport->PreviewPrimType, GEditorPerProjectIni); } void FMaterialEditor::RegenerateCodeView(bool bForce) { if (!bLivePreview && !bForce) { //When bLivePreview is false then the source can be out of date. return; } MaterialStatsManager->SignalMaterialChanged(); } void FMaterialEditor::UpdatePreviewMaterial( bool bForce ) { if (!bLivePreview && !bForce) { //Don't update the preview material return; } bStatsFromPreviewMaterial = true; if( PreviewExpression && ExpressionPreviewMaterial ) { PreviewExpression->ConnectToPreviewMaterial(ExpressionPreviewMaterial,0); } if(PreviewExpression) { check(ExpressionPreviewMaterial); // The preview material's expressions array must stay up to date before recompiling // So that RebuildMaterialFunctionInfo will see all the nested material functions that may need to be updated ExpressionPreviewMaterial->Expressions = Material->Expressions; FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread); UpdateContext.AddMaterial(ExpressionPreviewMaterial); // If we are previewing an expression, update the expression preview material ExpressionPreviewMaterial->PreEditChange( NULL ); ExpressionPreviewMaterial->PostEditChange(); } { FMaterialUpdateContext UpdateContext(FMaterialUpdateContext::EOptions::SyncWithRenderingThread); UpdateContext.AddMaterial(Material); // Update the regular preview material even when previewing an expression to allow code view regeneration. Material->PreEditChange(NULL); Material->PostEditChange(); } if (!PreviewExpression) { UpdateStatsMaterials(); // Null out the expression preview material so they can be GC'ed ExpressionPreviewMaterial = NULL; } if (MaterialEditorInstance != nullptr) { if (MaterialLayersFunctionsInstance.IsValid()) { MaterialLayersFunctionsInstance->SetEditorInstance(MaterialEditorInstance); MaterialLayersFunctionsInstance->Refresh(); } } MaterialStatsManager->SetMaterial(bStatsFromPreviewMaterial ? Material : OriginalMaterial); MaterialStatsManager->SignalMaterialChanged(); // Reregister all components that use the preview material, since UMaterial::PEC does not reregister components using a bIsPreviewMaterial=true material RefreshPreviewViewport(); } bool FMaterialEditor::UpdateOriginalMaterial() { // If the Material has compilation errors, warn the user for (int32 i = ERHIFeatureLevel::SM5; i >= 0; --i) { ERHIFeatureLevel::Type FeatureLevel = (ERHIFeatureLevel::Type)i; if( Material->GetMaterialResource(FeatureLevel)->GetCompileErrors().Num() > 0 ) { FString FeatureLevelName; GetFeatureLevelName(FeatureLevel, FeatureLevelName); if (Material->bUsedAsSpecialEngineMaterial) { FSuppressableWarningDialog::FSetupInfo Info( FText::Format(NSLOCTEXT("UnrealEd", "Error_CompileErrorsInDefaultMaterial", "The current material has compilation errors for feature level {0}.\nThis material is a Default Material which must be available as a code fallback at all times, compilation errors are not allowed."), FText::FromString(*FeatureLevelName)), NSLOCTEXT("UnrealEd", "Warning_CompileErrorsInDefaultMaterial_Title", "Error: Compilation errors in Default Material"), "Error_CompileErrorsInDefaultMaterial"); Info.ConfirmText = NSLOCTEXT("ModalDialogs", "CompileErrorsInDefaultMaterialOk", "Ok"); FSuppressableWarningDialog CompileErrors(Info); CompileErrors.ShowModal(); return false; } else { FSuppressableWarningDialog::FSetupInfo Info( FText::Format(NSLOCTEXT("UnrealEd", "Warning_CompileErrorsInMaterial", "The current material has compilation errors, so it will not render correctly in feature level {0}.\nAre you sure you wish to continue?"),FText::FromString(*FeatureLevelName)), NSLOCTEXT("UnrealEd", "Warning_CompileErrorsInMaterial_Title", "Warning: Compilation errors in this Material" ), "Warning_CompileErrorsInMaterial"); Info.ConfirmText = NSLOCTEXT("ModalDialogs", "CompileErrorsInMaterialConfirm", "Continue"); Info.CancelText = NSLOCTEXT("ModalDialogs", "CompileErrorsInMaterialCancel", "Abort"); FSuppressableWarningDialog CompileErrorsWarning( Info ); if( CompileErrorsWarning.ShowModal() == FSuppressableWarningDialog::Cancel ) { return false; } } } } // Make sure any graph position changes that might not have been copied are taken into account Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); //remove any memory copies of shader files, so they will be reloaded from disk //this way the material editor can be used for quick shader iteration FlushShaderFileCache(); //recompile and refresh the preview material so it will be updated if there was a shader change //Force it even if bLivePreview is false. UpdatePreviewMaterial(true); RegenerateCodeView(true); const FScopedBusyCursor BusyCursor; const FText LocalizedMaterialEditorApply = NSLOCTEXT("UnrealEd", "ToolTip_MaterialEditorApply", "Apply changes to original material and its use in the world."); GWarn->BeginSlowTask( LocalizedMaterialEditorApply, true ); GWarn->StatusUpdate( 1, 1, LocalizedMaterialEditorApply ); // Handle propagation of the material function being edited if (MaterialFunction) { // Copy the expressions back from the preview material MaterialFunction->FunctionExpressions = Material->Expressions; MaterialFunction->FunctionEditorComments = Material->EditorComments; // Preserve the thumbnail info UThumbnailInfo* OriginalThumbnailInfo = MaterialFunction->ParentFunction->ThumbnailInfo; UThumbnailInfo* ThumbnailInfo = MaterialFunction->ThumbnailInfo; MaterialFunction->ParentFunction->ThumbnailInfo = NULL; MaterialFunction->ThumbnailInfo = NULL; // overwrite the original material function in place by constructing a new one with the same name MaterialFunction->ParentFunction = (UMaterialFunction*)StaticDuplicateObject( MaterialFunction, MaterialFunction->ParentFunction->GetOuter(), MaterialFunction->ParentFunction->GetFName(), RF_AllFlags, MaterialFunction->ParentFunction->GetClass()); // Restore the thumbnail info MaterialFunction->ParentFunction->ThumbnailInfo = OriginalThumbnailInfo; MaterialFunction->ThumbnailInfo = ThumbnailInfo; // Restore RF_Standalone on the original material function, as it had been removed from the preview material so that it could be GC'd. MaterialFunction->ParentFunction->SetFlags( RF_Standalone ); for (int32 ExpressionIndex = 0; ExpressionIndex < MaterialFunction->ParentFunction->FunctionExpressions.Num(); ExpressionIndex++) { UMaterialExpression* CurrentExpression = MaterialFunction->ParentFunction->FunctionExpressions[ExpressionIndex]; ensureMsgf(CurrentExpression, TEXT("Invalid expression at index [%i] whilst saving material function."), ExpressionIndex); // Link the expressions back to their function if (CurrentExpression) { CurrentExpression->Material = NULL; CurrentExpression->Function = MaterialFunction->ParentFunction; } } for (int32 ExpressionIndex = 0; ExpressionIndex < MaterialFunction->ParentFunction->FunctionEditorComments.Num(); ExpressionIndex++) { UMaterialExpressionComment* CurrentExpression = MaterialFunction->ParentFunction->FunctionEditorComments[ExpressionIndex]; ensureMsgf(CurrentExpression, TEXT("Invalid comment at index [%i] whilst saving material function."), ExpressionIndex); // Link the expressions back to their function if (CurrentExpression) { CurrentExpression->Material = NULL; CurrentExpression->Function = MaterialFunction->ParentFunction; } } // clear the dirty flag bMaterialDirty = false; bStatsFromPreviewMaterial = false; UMaterialEditingLibrary::UpdateMaterialFunction(MaterialFunction->ParentFunction, Material); } // Handle propagation of the material being edited else { FNavigationLockContext NavUpdateLock(ENavigationLockReason::MaterialUpdate); // ensure the original copy of the material is removed from the editor's selection set // or it will end up containing a stale, invalid entry if ( OriginalMaterial->IsSelected() ) { GEditor->GetSelectedObjects()->Deselect( OriginalMaterial ); } // Preserve the thumbnail info UThumbnailInfo* OriginalThumbnailInfo = OriginalMaterial->ThumbnailInfo; UThumbnailInfo* ThumbnailInfo = Material->ThumbnailInfo; OriginalMaterial->ThumbnailInfo = NULL; Material->ThumbnailInfo = NULL; // A bit hacky, but disable material compilation in post load when we duplicate the material. UMaterial::ForceNoCompilationInPostLoad(true); // overwrite the original material in place by constructing a new one with the same name OriginalMaterial = (UMaterial*)StaticDuplicateObject( Material, OriginalMaterial->GetOuter(), OriginalMaterial->GetFName(), RF_AllFlags, OriginalMaterial->GetClass()); // Post load has been called, allow materials to be compiled in PostLoad. UMaterial::ForceNoCompilationInPostLoad(false); // Restore the thumbnail info OriginalMaterial->ThumbnailInfo = OriginalThumbnailInfo; Material->ThumbnailInfo = ThumbnailInfo; // Change the original material object to the new original material OriginalMaterialObject = OriginalMaterial; // Restore RF_Standalone on the original material, as it had been removed from the preview material so that it could be GC'd. OriginalMaterial->SetFlags( RF_Standalone ); // Manually copy bUsedAsSpecialEngineMaterial as it is duplicate transient to prevent accidental creation of new special engine materials OriginalMaterial->bUsedAsSpecialEngineMaterial = Material->bUsedAsSpecialEngineMaterial; UMaterialEditingLibrary::RecompileMaterial(OriginalMaterial); // clear the dirty flag bMaterialDirty = false; bStatsFromPreviewMaterial = false; } GWarn->EndSlowTask(); return true; } void FMaterialEditor::UpdateMaterialinfoList_Old() { bool bForceDisplay = false; TArray< TSharedRef > Messages; TArray> TempMaterialInfoList; ERHIFeatureLevel::Type FeatureLevelsToDisplay[2]; int32 NumFeatureLevels = 0; // Always show basic features so that errors aren't hidden FeatureLevelsToDisplay[NumFeatureLevels++] = GMaxRHIFeatureLevel; UMaterial* MaterialForStats = bStatsFromPreviewMaterial ? Material : OriginalMaterial; for (int32 i = 0; i < NumFeatureLevels; ++i) { TArray CompileErrors; ERHIFeatureLevel::Type FeatureLevel = FeatureLevelsToDisplay[i]; const FMaterialResource* MaterialResource = MaterialForStats->GetMaterialResource(FeatureLevel); if (MaterialFunction && ExpressionPreviewMaterial) { bool bHasValidOutput = true; int32 NumInputs = 0; int32 NumOutputs = 0; // For Material Layers if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayer) { // Material layers must have a single MA input and output only for (UMaterialExpression* Expression : *MaterialFunction->GetFunctionExpressions()) { if (UMaterialExpressionFunctionInput* InputExpression = Cast(Expression)) { ++NumInputs; if (NumInputs > 1 || !InputExpression->IsResultMaterialAttributes(0)) { CompileErrors.Add(TEXT("Layer graphs only support a single material attributes input.")); } } else if (UMaterialExpressionFunctionOutput* OutputExpression = Cast(Expression)) { ++NumOutputs; if (NumOutputs > 1 || !OutputExpression->IsResultMaterialAttributes(0)) { CompileErrors.Add(TEXT("Layer graphs only support a single material attributes output.")); } } else if (UMaterialExpressionMaterialAttributeLayers* RecursiveLayer = Cast(Expression)) { CompileErrors.Add(TEXT("Layer graphs do not support layers within layers.")); } } if (NumInputs > 1 || NumOutputs < 1) { CompileErrors.Add(TEXT("Layer graphs require a single material attributes output and optionally, a single material attributes input.")); } } else if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayerBlend) { // Material layer blends can have two MA inputs and single MA output only for (UMaterialExpression* Expression : *MaterialFunction->GetFunctionExpressions()) { if (UMaterialExpressionFunctionInput* InputExpression = Cast(Expression)) { ++NumInputs; if (NumInputs > 2 || !InputExpression->IsResultMaterialAttributes(0)) { CompileErrors.Add(TEXT("Layer blend graphs only support two material attributes inputs.")); } } else if (UMaterialExpressionFunctionOutput* OutputExpression = Cast(Expression)) { ++NumOutputs; if (NumOutputs > 1 || !OutputExpression->IsResultMaterialAttributes(0)) { CompileErrors.Add(TEXT("Layer blend graphs only support a single material attributes output.")); } } else if (UMaterialExpressionMaterialAttributeLayers* RecursiveLayer = Cast(Expression)) { CompileErrors.Add(TEXT("Layer blend graphs do not support layers within layers.")); } } if (NumOutputs < 1) { CompileErrors.Add(TEXT("Layer blend graphs can have up to two material attributes inputs and a single output.")); } } else { // Add a compile error message for functions missing an output CompileErrors = ExpressionPreviewMaterial->GetMaterialResource(FeatureLevel)->GetCompileErrors(); bool bFoundFunctionOutput = false; for (int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ExpressionIndex++) { if (Material->Expressions[ExpressionIndex]->IsA(UMaterialExpressionFunctionOutput::StaticClass())) { bFoundFunctionOutput = true; break; } } if (!bFoundFunctionOutput) { CompileErrors.Add(TEXT("Missing a function output")); } } } else { CompileErrors = MaterialResource->GetCompileErrors(); } // Only show general info if there are no errors and stats are enabled - Stats show for Materials, layers and blends if (CompileErrors.Num() == 0 && (!MaterialFunction || MaterialFunction->GetMaterialFunctionUsage() != Default)) { TArray Results; TArray EmptyMaterialResults; FMaterialStatsUtils::GetRepresentativeInstructionCounts(Results, MaterialResource); //Built in stats is no longer exposed to the UI but may still be useful so they're still in the code. bool bBuiltinStats = false; const FMaterialResource* EmptyMaterialResource = EmptyMaterial ? EmptyMaterial->GetMaterialResource(FeatureLevel) : nullptr; if (bShowBuiltinStats && bStatsFromPreviewMaterial && EmptyMaterialResource && Results.Num() > 0) { FMaterialStatsUtils::GetRepresentativeInstructionCounts(EmptyMaterialResults, EmptyMaterialResource); if (EmptyMaterialResults.Num() > 0) { //The instruction counts should match. If not, the preview material has been changed without the EmptyMaterial being updated to match. if (ensure(Results.Num() == EmptyMaterialResults.Num())) { bBuiltinStats = true; } } } for (int32 InstructionIndex = 0; InstructionIndex < Results.Num(); InstructionIndex++) { FString InstructionCountString = FString::Printf(TEXT("%s: %u instructions"), *Results[InstructionIndex].ShaderDescription, Results[InstructionIndex].InstructionCount); if (bBuiltinStats) { InstructionCountString += FString::Printf(TEXT(" - Built-in instructions: %u"), EmptyMaterialResults[InstructionIndex].InstructionCount); } TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(InstructionCountString, FLinearColor::Yellow))); TSharedRef Line = FTokenizedMessage::Create(EMessageSeverity::Info); Line->AddToken(FTextToken::Create(FText::FromString(InstructionCountString))); Messages.Add(Line); } // Display the number of samplers used by the material. const int32 SamplersUsed = MaterialResource->GetSamplerUsage(); if (SamplersUsed >= 0) { int32 MaxSamplers = GetExpectedFeatureLevelMaxTextureSamplers(MaterialResource->GetFeatureLevel()); FString SamplersString = FString::Printf(TEXT("%s samplers: %u/%u"), FeatureLevel <= ERHIFeatureLevel::ES3_1 ? TEXT("Mobile texture") : TEXT("Texture"), SamplersUsed, MaxSamplers); TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(SamplersString, FLinearColor::Yellow))); TSharedRef Line = FTokenizedMessage::Create( EMessageSeverity::Info ); Line->AddToken( FTextToken::Create( FText::FromString( SamplersString ) ) ); Messages.Add(Line); } // Display estimated texture look-up/sample counts uint32 NumVSTextureSamples = 0, NumPSTextureSamples = 0; MaterialResource->GetEstimatedNumTextureSamples(NumVSTextureSamples, NumPSTextureSamples); if (NumVSTextureSamples > 0 || NumPSTextureSamples > 0) { FString SamplesString = FString::Printf(TEXT("Texture Lookups (Est.): VS(%u), PS(%u)"), NumVSTextureSamples, NumPSTextureSamples); TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(SamplesString, FLinearColor::Yellow))); TSharedRef Line = FTokenizedMessage::Create(EMessageSeverity::Info); Line->AddToken(FTextToken::Create(FText::FromString(SamplesString))); Messages.Add(Line); } // Display the number of custom/user interpolators used by the material. uint32 UVScalarsUsed, CustomInterpolatorScalarsUsed; MaterialResource->GetUserInterpolatorUsage(UVScalarsUsed, CustomInterpolatorScalarsUsed); if (UVScalarsUsed > 0 || CustomInterpolatorScalarsUsed > 0) { uint32 TotalScalars = UVScalarsUsed + CustomInterpolatorScalarsUsed; uint32 MaxScalars = FMath::DivideAndRoundUp(TotalScalars, 4u) * 4; FString InterpolatorsString = FString::Printf(TEXT("User interpolators: %u/%u Scalars (%u/4 Vectors) (TexCoords: %i, Custom: %i)"), TotalScalars, MaxScalars, MaxScalars / 4, UVScalarsUsed, CustomInterpolatorScalarsUsed); TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(InterpolatorsString, FLinearColor::Yellow))); TSharedRef Line = FTokenizedMessage::Create( EMessageSeverity::Info ); Line->AddToken(FTextToken::Create(FText::FromString(InterpolatorsString))); Messages.Add(Line); } } FString FeatureLevelName; GetFeatureLevelName(FeatureLevel,FeatureLevelName); for(int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++) { FString ErrorString = FString::Printf(TEXT("[%s] %s"), *FeatureLevelName, *CompileErrors[ErrorIndex]); TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(ErrorString, FLinearColor::Red))); TSharedRef Line = FTokenizedMessage::Create( EMessageSeverity::Error ); Line->AddToken( FTextToken::Create( FText::FromString( ErrorString ) ) ); Messages.Add(Line); bForceDisplay = true; } } bool bNeedsRefresh = false; if (TempMaterialInfoList.Num() != MaterialInfoList.Num()) { bNeedsRefresh = true; } for (int32 Index = 0; !bNeedsRefresh && Index < TempMaterialInfoList.Num(); ++Index) { if (TempMaterialInfoList[Index]->Color != MaterialInfoList[Index]->Color) { bNeedsRefresh = true; break; } if (TempMaterialInfoList[Index]->Text != MaterialInfoList[Index]->Text) { bNeedsRefresh = true; break; } } if (bNeedsRefresh) { MaterialInfoList = TempMaterialInfoList; auto Listing = MaterialStatsManager->GetOldStatsListing(); Listing->ClearMessages(); Listing->AddMessages(Messages); if (bForceDisplay) { TabManager->InvokeTab(MaterialStatsManager->GetGridOldStatsTabName()); } } } void FMaterialEditor::UpdateMaterialInfoList() { // setup stats widget visibility if (MaterialFunction) { return; } UMaterial* MaterialForStats = bStatsFromPreviewMaterial ? Material : OriginalMaterial; // check for errors TArray CompileErrors; if (MaterialFunction && ExpressionPreviewMaterial) { // Add a compile error message for functions missing an output CompileErrors = ExpressionPreviewMaterial->GetMaterialResource(GMaxRHIFeatureLevel)->GetCompileErrors(); bool bFoundFunctionOutput = false; for (int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ExpressionIndex++) { if (Material->Expressions[ExpressionIndex]->IsA(UMaterialExpressionFunctionOutput::StaticClass())) { bFoundFunctionOutput = true; break; } } if (!bFoundFunctionOutput) { CompileErrors.Add(TEXT("Missing a function output")); } } // compute error crc FString NewErrorHash; for (int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++) { NewErrorHash += FMD5::HashAnsiString(*CompileErrors[ErrorIndex]); } TSharedPtr TitleBar = GraphEditor->GetTitleBar(); if (NewErrorHash != MaterialErrorHash) { MaterialErrorHash = NewErrorHash; MaterialInfoList.Reset(); TArray< TSharedRef > Messages; FString FeatureLevelName; GetFeatureLevelName(GMaxRHIFeatureLevel, FeatureLevelName); for (int32 ErrorIndex = 0; ErrorIndex < CompileErrors.Num(); ErrorIndex++) { FString ErrorString = FString::Printf(TEXT("[%s] %s"), *FeatureLevelName, *CompileErrors[ErrorIndex]); TSharedRef Line = FTokenizedMessage::Create(EMessageSeverity::Error); Line->AddToken(FTextToken::Create(FText::FromString(ErrorString))); Messages.Add(Line); MaterialInfoList.Add(MakeShareable(new FMaterialInfo(ErrorString, FLinearColor::Red))); } StatsListing->ClearMessages(); StatsListing->AddMessages(Messages); StaticCastSharedPtr(TitleBar)->RequestRefresh(); TitleBar->SetVisibility(EVisibility::Visible); } else if (!CompileErrors.Num()) { TitleBar->SetVisibility(EVisibility::Collapsed); } // extract material stats MaterialStatsManager->SetMaterial(MaterialForStats); MaterialStatsManager->Update(); } void FMaterialEditor::UpdateGraphNodeStates() { const FMaterialResource* ErrorMaterialResource = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(GMaxRHIFeatureLevel) : Material->GetMaterialResource(GMaxRHIFeatureLevel); const FMaterialResource* ErrorMaterialResourceES2 = NULL; bool bUpdatedErrorState = false; bool bToggledVisibleState = bPreviewFeaturesChanged; bool bShowAllNodes = true; TArray VisibleExpressions; if (bPreviewFeaturesChanged) { Material->GetAllReferencedExpressions(VisibleExpressions, nullptr, NodeFeatureLevel, NodeQualityLevel); if (NodeFeatureLevel != ERHIFeatureLevel::Num || NodeQualityLevel != EMaterialQualityLevel::Num) { bShowAllNodes = false; } } // Have to loop through everything here as there's no way to be notified when the material resource updates for (int32 Index = 0; Index < Material->MaterialGraph->Nodes.Num(); ++Index) { UMaterialGraphNode* MaterialNode = Cast(Material->MaterialGraph->Nodes[Index]); if (MaterialNode) { MaterialNode->bIsPreviewExpression = (PreviewExpression == MaterialNode->MaterialExpression); MaterialNode->bIsErrorExpression = (ErrorMaterialResource->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE) || (ErrorMaterialResourceES2 && ErrorMaterialResourceES2->GetErrorExpressions().Find(MaterialNode->MaterialExpression) != INDEX_NONE); if (MaterialNode->bIsErrorExpression && !MaterialNode->bHasCompilerMessage) { check(MaterialNode->MaterialExpression); bUpdatedErrorState = true; MaterialNode->bHasCompilerMessage = true; MaterialNode->ErrorMsg = MaterialNode->MaterialExpression->LastErrorText; MaterialNode->ErrorType = EMessageSeverity::Error; } else if (!MaterialNode->bIsErrorExpression && MaterialNode->bHasCompilerMessage) { bUpdatedErrorState = true; MaterialNode->bHasCompilerMessage = false; } if (MaterialNode->MaterialExpression && bPreviewFeaturesChanged) { if ((bShowAllNodes || VisibleExpressions.Contains(MaterialNode->MaterialExpression))) { MaterialNode->SetForceDisplayAsDisabled(false); } else if (!bShowAllNodes && !VisibleExpressions.Contains(MaterialNode->MaterialExpression)) { MaterialNode->SetForceDisplayAsDisabled(true); } } } } bPreviewFeaturesChanged = false; if (bUpdatedErrorState || bToggledVisibleState) { // Rebuild the SGraphNodes to display/hide error block GraphEditor->NotifyGraphChanged(); } } void FMaterialEditor::AddReferencedObjects( FReferenceCollector& Collector ) { Collector.AddReferencedObject(EditorOptions); Collector.AddReferencedObject(Material); Collector.AddReferencedObject(OriginalMaterial); Collector.AddReferencedObject(MaterialFunction); Collector.AddReferencedObject(ExpressionPreviewMaterial); Collector.AddReferencedObject(EmptyMaterial); Collector.AddReferencedObject(MaterialEditorInstance); Collector.AddReferencedObject(PreviewExpression); for (FMatExpressionPreview& ExpressionPreview : ExpressionPreviews) { ExpressionPreview.AddReferencedObjects(Collector); } } void FMaterialEditor::BindCommands() { const FMaterialEditorCommands& Commands = FMaterialEditorCommands::Get(); ToolkitCommands->MapAction( Commands.Apply, FExecuteAction::CreateSP( this, &FMaterialEditor::OnApply ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::OnApplyEnabled ) ); ToolkitCommands->MapAction( FEditorViewportCommands::Get().ToggleRealTime, FExecuteAction::CreateSP( PreviewViewport.ToSharedRef(), &SMaterialEditor3DPreviewViewport::OnToggleRealtime ), FCanExecuteAction(), FIsActionChecked::CreateSP( PreviewViewport.ToSharedRef(), &SMaterialEditor3DPreviewViewport::IsRealtime ) ); ToolkitCommands->MapAction( FGenericCommands::Get().Undo, FExecuteAction::CreateSP(this, &FMaterialEditor::UndoGraphAction)); ToolkitCommands->MapAction( FGenericCommands::Get().Redo, FExecuteAction::CreateSP(this, &FMaterialEditor::RedoGraphAction)); ToolkitCommands->MapAction( Commands.CameraHome, FExecuteAction::CreateSP(this, &FMaterialEditor::OnCameraHome), FCanExecuteAction() ); ToolkitCommands->MapAction( Commands.CleanUnusedExpressions, FExecuteAction::CreateSP(this, &FMaterialEditor::CleanUnusedExpressions), FCanExecuteAction() ); ToolkitCommands->MapAction( Commands.ShowHideConnectors, FExecuteAction::CreateSP(this, &FMaterialEditor::OnShowConnectors), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsOnShowConnectorsChecked)); ToolkitCommands->MapAction( Commands.ToggleLivePreview, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleLivePreview), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleLivePreviewChecked)); ToolkitCommands->MapAction( Commands.ToggleRealtimeExpressions, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleRealTimeExpressions), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleRealTimeExpressionsChecked)); ToolkitCommands->MapAction( Commands.AlwaysRefreshAllPreviews, FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlwaysRefreshAllPreviews), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsOnAlwaysRefreshAllPreviews)); ToolkitCommands->MapAction( Commands.UseCurrentTexture, FExecuteAction::CreateSP(this, &FMaterialEditor::OnUseCurrentTexture)); ToolkitCommands->MapAction( Commands.ConvertObjects, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects)); ToolkitCommands->MapAction( Commands.ConvertToTextureObjects, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures)); ToolkitCommands->MapAction( Commands.ConvertToTextureSamples, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures)); ToolkitCommands->MapAction( Commands.ConvertToConstant, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects)); ToolkitCommands->MapAction( Commands.StopPreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode)); ToolkitCommands->MapAction( Commands.StartPreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode)); ToolkitCommands->MapAction( Commands.EnableRealtimePreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview)); ToolkitCommands->MapAction( Commands.DisableRealtimePreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview)); ToolkitCommands->MapAction( Commands.SelectDownstreamNodes, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectDownstreamNodes)); ToolkitCommands->MapAction( Commands.SelectUpstreamNodes, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectUpstreamNodes)); ToolkitCommands->MapAction( Commands.RemoveFromFavorites, FExecuteAction::CreateSP(this, &FMaterialEditor::RemoveSelectedExpressionFromFavorites)); ToolkitCommands->MapAction( Commands.AddToFavorites, FExecuteAction::CreateSP(this, &FMaterialEditor::AddSelectedExpressionToFavorites)); ToolkitCommands->MapAction( Commands.ForceRefreshPreviews, FExecuteAction::CreateSP(this, &FMaterialEditor::OnForceRefreshPreviews)); ToolkitCommands->MapAction( Commands.FindInMaterial, FExecuteAction::CreateSP(this, &FMaterialEditor::OnFindInMaterial)); ToolkitCommands->MapAction( Commands.QualityLevel_All, FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Num), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Num)); ToolkitCommands->MapAction( Commands.QualityLevel_High, FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::High), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::High)); ToolkitCommands->MapAction( Commands.QualityLevel_Medium, FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Medium), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Medium)); ToolkitCommands->MapAction( Commands.QualityLevel_Low, FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Low), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Low)); ToolkitCommands->MapAction( Commands.FeatureLevel_All, FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::Num), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::Num)); ToolkitCommands->MapAction( Commands.FeatureLevel_ES2, FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::ES2), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::ES2)); ToolkitCommands->MapAction( Commands.FeatureLevel_ES31, FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::ES3_1), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::ES3_1)); ToolkitCommands->MapAction( Commands.FeatureLevel_SM4, FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::SM4), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::SM4)); ToolkitCommands->MapAction( Commands.FeatureLevel_SM5, FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::SM5), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::SM5)); } void FMaterialEditor::OnApply() { UE_LOG(LogMaterialEditor, Log, TEXT("Applying material %s"), *GetEditingObjects()[0]->GetName()); UpdateOriginalMaterial(); GEditor->OnSceneMaterialsModified(); } bool FMaterialEditor::OnApplyEnabled() const { return bMaterialDirty == true; } void FMaterialEditor::OnCameraHome() { RecenterEditor(); } void FMaterialEditor::OnShowConnectors() { bHideUnusedConnectors = !bHideUnusedConnectors; GraphEditor->SetPinVisibility(bHideUnusedConnectors ? SGraphEditor::Pin_HideNoConnection : SGraphEditor::Pin_Show); } bool FMaterialEditor::IsOnShowConnectorsChecked() const { return bHideUnusedConnectors == false; } void FMaterialEditor::ToggleLivePreview() { bLivePreview = !bLivePreview; if (bLivePreview) { UpdatePreviewMaterial(); RegenerateCodeView(); } } bool FMaterialEditor::IsToggleLivePreviewChecked() const { return bLivePreview; } void FMaterialEditor::ToggleRealTimeExpressions() { bIsRealtime = !bIsRealtime; } bool FMaterialEditor::IsToggleRealTimeExpressionsChecked() const { return bIsRealtime == true; } void FMaterialEditor::OnAlwaysRefreshAllPreviews() { bAlwaysRefreshAllPreviews = !bAlwaysRefreshAllPreviews; if ( bAlwaysRefreshAllPreviews ) { RefreshExpressionPreviews(); } } bool FMaterialEditor::IsOnAlwaysRefreshAllPreviews() const { return bAlwaysRefreshAllPreviews == true; } void FMaterialEditor::OnUseCurrentTexture() { // Set the currently selected texture in the generic browser // as the texture to use in all selected texture sample expressions. FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); UTexture* SelectedTexture = GEditor->GetSelectedObjects()->GetTop(); if ( SelectedTexture ) { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "UseCurrentTexture", "Use Current Texture") ); const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode && GraphNode->MaterialExpression->IsA(UMaterialExpressionTextureBase::StaticClass()) ) { UMaterialExpressionTextureBase* TextureBase = static_cast(GraphNode->MaterialExpression); TextureBase->Modify(); TextureBase->Texture = SelectedTexture; TextureBase->AutoSetSampleType(); } } // Update the current preview material. UpdatePreviewMaterial(); Material->MarkPackageDirty(); RegenerateCodeView(); RefreshExpressionPreviews(); SetMaterialDirty(); } } void FMaterialEditor::OnConvertObjects() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("MaterialEditorConvert", "Material Editor: Convert") ); Material->Modify(); Material->MaterialGraph->Modify(); TArray NodesToDelete; TArray NodesToSelect; for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { // Look for the supported classes to convert from UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression; UMaterialExpressionConstant* Constant1Expression = Cast(CurrentSelectedExpression); UMaterialExpressionConstant2Vector* Constant2Expression = Cast(CurrentSelectedExpression); UMaterialExpressionConstant3Vector* Constant3Expression = Cast(CurrentSelectedExpression); UMaterialExpressionConstant4Vector* Constant4Expression = Cast(CurrentSelectedExpression); UMaterialExpressionTextureSample* TextureSampleExpression = Cast(CurrentSelectedExpression); UMaterialExpressionTextureObject* TextureObjectExpression = Cast(CurrentSelectedExpression); UMaterialExpressionComponentMask* ComponentMaskExpression = Cast(CurrentSelectedExpression); UMaterialExpressionParticleSubUV* ParticleSubUVExpression = Cast(CurrentSelectedExpression); UMaterialExpressionScalarParameter* ScalarParameterExpression = Cast(CurrentSelectedExpression); UMaterialExpressionVectorParameter* VectorParameterExpression = Cast(CurrentSelectedExpression); UMaterialExpressionTextureObjectParameter* TextureObjectParameterExpression = Cast(CurrentSelectedExpression); // Setup the class to convert to UClass* ClassToCreate = NULL; if (Constant1Expression) { ClassToCreate = UMaterialExpressionScalarParameter::StaticClass(); } else if (Constant2Expression || Constant3Expression || Constant4Expression) { ClassToCreate = UMaterialExpressionVectorParameter::StaticClass(); } else if (ParticleSubUVExpression) // Has to come before the TextureSample comparison... { ClassToCreate = UMaterialExpressionTextureSampleParameterSubUV::StaticClass(); } else if (TextureSampleExpression && TextureSampleExpression->Texture && TextureSampleExpression->Texture->IsA(UTextureCube::StaticClass())) { ClassToCreate = UMaterialExpressionTextureSampleParameterCube::StaticClass(); } else if (TextureObjectExpression) { ClassToCreate = UMaterialExpressionTextureObjectParameter::StaticClass(); } else if (TextureObjectParameterExpression) // Has to come before TextureSample comparison { ClassToCreate = UMaterialExpressionTextureObject::StaticClass(); } else if (TextureSampleExpression) { ClassToCreate = UMaterialExpressionTextureSampleParameter2D::StaticClass(); } else if (ComponentMaskExpression) { ClassToCreate = UMaterialExpressionStaticComponentMaskParameter::StaticClass(); } else if (ScalarParameterExpression) { ClassToCreate = UMaterialExpressionConstant::StaticClass(); } else if (VectorParameterExpression) { // Technically should be a constant 4 but UMaterialExpressionVectorParameter has an rgb pin, so using Constant3 to avoid a compile error ClassToCreate = UMaterialExpressionConstant3Vector::StaticClass(); } if (ClassToCreate) { UMaterialExpression* NewExpression = CreateNewMaterialExpression(ClassToCreate, FVector2D(GraphNode->NodePosX, GraphNode->NodePosY), true, true ); if (NewExpression) { UMaterialGraphNode* NewGraphNode = CastChecked(NewExpression->GraphNode); NewGraphNode->ReplaceNode(GraphNode); bool bNeedsRefresh = false; // Copy over any common values if (GraphNode->NodeComment.Len() > 0) { bNeedsRefresh = true; NewGraphNode->NodeComment = GraphNode->NodeComment; } // Copy over expression-specific values if (Constant1Expression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = Constant1Expression->R; } else if (Constant2Expression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = FLinearColor(Constant2Expression->R, Constant2Expression->G, 0); } else if (Constant3Expression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = Constant3Expression->Constant; CastChecked(NewExpression)->DefaultValue.A = 1.0f; } else if (Constant4Expression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = Constant4Expression->Constant; } else if (TextureSampleExpression && !TextureObjectParameterExpression) { bNeedsRefresh = true; UMaterialExpressionTextureSampleParameter* NewTextureExpr = CastChecked(NewExpression); NewTextureExpr->Texture = TextureSampleExpression->Texture; NewTextureExpr->Coordinates = TextureSampleExpression->Coordinates; NewTextureExpr->AutoSetSampleType(); NewTextureExpr->IsDefaultMeshpaintTexture = TextureSampleExpression->IsDefaultMeshpaintTexture; NewTextureExpr->TextureObject = TextureSampleExpression->TextureObject; NewTextureExpr->MipValue = TextureSampleExpression->MipValue; NewTextureExpr->CoordinatesDX = TextureSampleExpression->CoordinatesDX; NewTextureExpr->CoordinatesDY = TextureSampleExpression->CoordinatesDY; NewTextureExpr->MipValueMode = TextureSampleExpression->MipValueMode; NewGraphNode->ReconstructNode(); } else if (ComponentMaskExpression) { bNeedsRefresh = true; UMaterialExpressionStaticComponentMaskParameter* ComponentMask = CastChecked(NewExpression); ComponentMask->DefaultR = ComponentMaskExpression->R; ComponentMask->DefaultG = ComponentMaskExpression->G; ComponentMask->DefaultB = ComponentMaskExpression->B; ComponentMask->DefaultA = ComponentMaskExpression->A; } else if (ParticleSubUVExpression) { bNeedsRefresh = true; CastChecked(NewExpression)->Texture = ParticleSubUVExpression->Texture; } else if (ScalarParameterExpression) { bNeedsRefresh = true; CastChecked(NewExpression)->R = ScalarParameterExpression->DefaultValue; } else if (VectorParameterExpression) { bNeedsRefresh = true; CastChecked(NewExpression)->Constant = VectorParameterExpression->DefaultValue; } if (bNeedsRefresh) { // Refresh the expression preview if we changed its properties after it was created NewExpression->bNeedToUpdatePreview = true; RefreshExpressionPreview( NewExpression, true ); } NodesToDelete.AddUnique(GraphNode); NodesToSelect.Add(NewGraphNode); } } } } // Delete the replaced nodes DeleteNodes(NodesToDelete); // Select each of the newly converted expressions for ( TArray::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter ) { GraphEditor->SetNodeSelection(*NodeIter, true); } } } void FMaterialEditor::OnConvertTextures() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("MaterialEditorConvertTexture", "Material Editor: Convert to Texture") ); Material->Modify(); Material->MaterialGraph->Modify(); TArray NodesToDelete; TArray NodesToSelect; for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { // Look for the supported classes to convert from UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression; UMaterialExpressionTextureSample* TextureSampleExpression = Cast(CurrentSelectedExpression); UMaterialExpressionTextureObject* TextureObjectExpression = Cast(CurrentSelectedExpression); // Setup the class to convert to UClass* ClassToCreate = NULL; if (TextureSampleExpression) { ClassToCreate = UMaterialExpressionTextureObject::StaticClass(); } else if (TextureObjectExpression) { ClassToCreate = UMaterialExpressionTextureSample::StaticClass(); } if (ClassToCreate) { UMaterialExpression* NewExpression = CreateNewMaterialExpression(ClassToCreate, FVector2D(GraphNode->NodePosX, GraphNode->NodePosY), false, true); if (NewExpression) { UMaterialGraphNode* NewGraphNode = CastChecked(NewExpression->GraphNode); NewGraphNode->ReplaceNode(GraphNode); bool bNeedsRefresh = false; // Copy over expression-specific values if (TextureSampleExpression) { bNeedsRefresh = true; UMaterialExpressionTextureObject* NewTextureExpr = CastChecked(NewExpression); NewTextureExpr->Texture = TextureSampleExpression->Texture; NewTextureExpr->AutoSetSampleType(); NewTextureExpr->IsDefaultMeshpaintTexture = TextureSampleExpression->IsDefaultMeshpaintTexture; } else if (TextureObjectExpression) { bNeedsRefresh = true; UMaterialExpressionTextureSample* NewTextureExpr = CastChecked(NewExpression); NewTextureExpr->Texture = TextureObjectExpression->Texture; NewTextureExpr->AutoSetSampleType(); NewTextureExpr->IsDefaultMeshpaintTexture = TextureObjectExpression->IsDefaultMeshpaintTexture; NewTextureExpr->MipValueMode = TMVM_None; } if (bNeedsRefresh) { // Refresh the expression preview if we changed its properties after it was created NewExpression->bNeedToUpdatePreview = true; RefreshExpressionPreview( NewExpression, true ); } NodesToDelete.AddUnique(GraphNode); NodesToSelect.Add(NewGraphNode); } } } } // Delete the replaced nodes DeleteNodes(NodesToDelete); // Select each of the newly converted expressions for ( TArray::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter ) { GraphEditor->SetNodeSelection(*NodeIter, true); } } } void FMaterialEditor::OnPreviewNode() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { GraphEditor->NotifyGraphChanged(); SetPreviewExpression(GraphNode->MaterialExpression); } } } } void FMaterialEditor::OnToggleRealtimePreview() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { UMaterialExpression* SelectedExpression = GraphNode->MaterialExpression; SelectedExpression->bRealtimePreview = !SelectedExpression->bRealtimePreview; if (SelectedExpression->bRealtimePreview) { SelectedExpression->bCollapsed = false; } RefreshExpressionPreviews(); SetMaterialDirty(); } } } } void FMaterialEditor::OnSelectDownstreamNodes() { TArray NodesToCheck; TArray CheckedNodes; TArray NodesToSelect; const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { NodesToCheck.Add(GraphNode); } } while (NodesToCheck.Num() > 0) { UMaterialGraphNode* CurrentNode = NodesToCheck.Last(); TArray OutputPins; CurrentNode->GetOutputPins(OutputPins); for (int32 Index = 0; Index < OutputPins.Num(); ++Index) { for (int32 LinkIndex = 0; LinkIndex < OutputPins[Index]->LinkedTo.Num(); ++LinkIndex) { UMaterialGraphNode* LinkedNode = Cast(OutputPins[Index]->LinkedTo[LinkIndex]->GetOwningNode()); if (LinkedNode) { int32 FoundIndex = -1; CheckedNodes.Find(LinkedNode, FoundIndex); if (FoundIndex < 0) { NodesToSelect.Add(LinkedNode); NodesToCheck.Add(LinkedNode); } } } } // This graph node has now been examined CheckedNodes.Add(CurrentNode); NodesToCheck.Remove(CurrentNode); } for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index) { GraphEditor->SetNodeSelection(NodesToSelect[Index], true); } } void FMaterialEditor::OnSelectUpstreamNodes() { TArray NodesToCheck; TArray CheckedNodes; TArray NodesToSelect; const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { NodesToCheck.Add(GraphNode); } } while (NodesToCheck.Num() > 0) { UMaterialGraphNode* CurrentNode = NodesToCheck.Last(); TArray InputPins; CurrentNode->GetInputPins(InputPins); for (int32 Index = 0; Index < InputPins.Num(); ++Index) { for (int32 LinkIndex = 0; LinkIndex < InputPins[Index]->LinkedTo.Num(); ++LinkIndex) { UMaterialGraphNode* LinkedNode = Cast(InputPins[Index]->LinkedTo[LinkIndex]->GetOwningNode()); if (LinkedNode) { int32 FoundIndex = -1; CheckedNodes.Find(LinkedNode, FoundIndex); if (FoundIndex < 0) { NodesToSelect.Add(LinkedNode); NodesToCheck.Add(LinkedNode); } } } } // This graph node has now been examined CheckedNodes.Add(CurrentNode); NodesToCheck.Remove(CurrentNode); } for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index) { GraphEditor->SetNodeSelection(NodesToSelect[Index], true); } } void FMaterialEditor::OnForceRefreshPreviews() { ForceRefreshExpressionPreviews(); RefreshPreviewViewport(); } void FMaterialEditor::OnCreateComment() { CreateNewMaterialExpressionComment(GraphEditor->GetPasteLocation()); } void FMaterialEditor::OnCreateComponentMaskNode() { CreateNewMaterialExpression(UMaterialExpressionComponentMask::StaticClass(), GraphEditor->GetPasteLocation(), true, false); } void FMaterialEditor::OnFindInMaterial() { TabManager->InvokeTab(FindTabId); FindResults->FocusForUse(); } UClass* FMaterialEditor::GetOnPromoteToParameterClass(UEdGraphPin* TargetPin) { UMaterialGraphNode_Root* RootPinNode = Cast(TargetPin->GetOwningNode()); UMaterialGraphNode* OtherPinNode = Cast(TargetPin->GetOwningNode()); if (RootPinNode != nullptr) { EMaterialProperty propertyId = (EMaterialProperty)FCString::Atoi(*TargetPin->PinType.PinSubCategory.ToString()); switch (propertyId) { case MP_Opacity: case MP_Metallic: case MP_Specular: case MP_Roughness: case MP_TessellationMultiplier: case MP_CustomData0: case MP_CustomData1: case MP_AmbientOcclusion: case MP_Refraction: case MP_PixelDepthOffset: case MP_ShadingModel: case MP_OpacityMask: return UMaterialExpressionScalarParameter::StaticClass(); case MP_WorldPositionOffset: case MP_WorldDisplacement: case MP_EmissiveColor: case MP_BaseColor: case MP_SubsurfaceColor: case MP_SpecularColor: case MP_Normal: return UMaterialExpressionVectorParameter::StaticClass(); } } else if (OtherPinNode) { const TArray ExpressionInputs = OtherPinNode->MaterialExpression->GetInputs(); FName TargetPinName = OtherPinNode->GetShortenPinName(TargetPin->PinName); for (int32 Index = 0; Index < ExpressionInputs.Num(); ++Index) { FExpressionInput* Input = ExpressionInputs[Index]; FName InputName = OtherPinNode->MaterialExpression->GetInputName(Index); InputName = OtherPinNode->GetShortenPinName(InputName); if (InputName == TargetPinName) { switch (OtherPinNode->MaterialExpression->GetInputType(Index)) { case MCT_Float1: case MCT_Float: return UMaterialExpressionScalarParameter::StaticClass(); case MCT_Float2: case MCT_Float3: case MCT_Float4: return UMaterialExpressionVectorParameter::StaticClass(); case MCT_StaticBool: return UMaterialExpressionStaticBoolParameter::StaticClass(); case MCT_Texture2D: case MCT_TextureCube: case MCT_VolumeTexture: case MCT_Texture: return UMaterialExpressionTextureObjectParameter::StaticClass(); } break; } } } return nullptr; } void FMaterialEditor::OnPromoteToParameter() { UEdGraphPin* TargetPin = GraphEditor->GetGraphPinForMenu(); UMaterialGraphNode_Base* PinNode = Cast(TargetPin->GetOwningNode()); FMaterialGraphSchemaAction_NewNode Action; Action.MaterialExpressionClass = GetOnPromoteToParameterClass(TargetPin); if (Action.MaterialExpressionClass != nullptr) { check(PinNode); UEdGraph* GraphObj = PinNode->GetGraph(); check(GraphObj); const FScopedTransaction Transaction(LOCTEXT("PromoteToParameter", "Promote To Parameter")); GraphObj->Modify(); // Set position of new node to be close to node we clicked on FVector2D NewNodePos; NewNodePos.X = PinNode->NodePosX - 100; NewNodePos.Y = PinNode->NodePosY; UMaterialGraphNode* MaterialNode = Cast(Action.PerformAction(GraphObj, TargetPin, NewNodePos)); if (MaterialNode->MaterialExpression->HasAParameterName()) { MaterialNode->MaterialExpression->SetParameterName(TargetPin->PinName); MaterialNode->MaterialExpression->ValidateParameterName(false); } } if (MaterialEditorInstance != nullptr) { MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance); } } bool FMaterialEditor::OnCanPromoteToParameter() { UEdGraphPin* TargetPin = GraphEditor->GetGraphPinForMenu(); if (ensure(TargetPin) && TargetPin->LinkedTo.Num() == 0) { return GetOnPromoteToParameterClass(TargetPin) != nullptr; } return false; } FString FMaterialEditor::GetDocLinkForSelectedNode() { FString DocumentationLink; TArray SelectedNodes = GraphEditor->GetSelectedNodes().Array(); if (SelectedNodes.Num() == 1) { UMaterialGraphNode* SelectedGraphNode = Cast(SelectedNodes[0]); if (SelectedGraphNode != NULL) { FString DocLink = SelectedGraphNode->GetDocumentationLink(); FString DocExcerpt = SelectedGraphNode->GetDocumentationExcerptName(); DocumentationLink = FEditorClassUtils::GetDocumentationLinkFromExcerpt(DocLink, DocExcerpt); } } return DocumentationLink; } void FMaterialEditor::OnGoToDocumentation() { FString DocumentationLink = GetDocLinkForSelectedNode(); if (!DocumentationLink.IsEmpty()) { IDocumentation::Get()->Open(DocumentationLink, FDocumentationSourceInfo(TEXT("rightclick_matnode"))); } } bool FMaterialEditor::CanGoToDocumentation() { FString DocumentationLink = GetDocLinkForSelectedNode(); return !DocumentationLink.IsEmpty(); } void FMaterialEditor::RenameAssetFromRegistry(const FAssetData& InAddedAssetData, const FString& InNewName) { // Grab the asset class, it will be checked for being a material function. UClass* Asset = FindObject(ANY_PACKAGE, *InAddedAssetData.AssetClass.ToString()); if(Asset->IsChildOf(UMaterialFunction::StaticClass())) { ForceRefreshExpressionPreviews(); } } void FMaterialEditor::OnMaterialUsageFlagsChanged(UMaterial* MaterialThatChanged, int32 FlagThatChanged) { EMaterialUsage Flag = static_cast(FlagThatChanged); if(MaterialThatChanged == OriginalMaterial) { bool bNeedsRecompile = false; Material->SetMaterialUsage(bNeedsRecompile, Flag); UpdateStatsMaterials(); } } void FMaterialEditor::SetVectorParameterDefaultOnDependentMaterials(FName ParameterName, const FLinearColor& Value, bool bOverride) { TArray MaterialsToOverride; if (MaterialFunction) { // Find all materials that reference this function for (TObjectIterator It; It; ++It) { UMaterial* CurrentMaterial = *It; if (CurrentMaterial != Material) { bool bUpdate = false; for (int32 FunctionIndex = 0; FunctionIndex < CurrentMaterial->MaterialFunctionInfos.Num(); FunctionIndex++) { if (CurrentMaterial->MaterialFunctionInfos[FunctionIndex].Function == MaterialFunction->ParentFunction) { bUpdate = true; break; } } if (bUpdate) { MaterialsToOverride.Add(CurrentMaterial); } } } } else { MaterialsToOverride.Add(OriginalMaterial); } const ERHIFeatureLevel::Type FeatureLevel = GEditor->GetEditorWorldContext().World()->FeatureLevel; for (int32 MaterialIndex = 0; MaterialIndex < MaterialsToOverride.Num(); MaterialIndex++) { UMaterial* CurrentMaterial = MaterialsToOverride[MaterialIndex]; CurrentMaterial->OverrideVectorParameterDefault(ParameterName, Value, bOverride, FeatureLevel); } // Update MI's that reference any of the materials affected for (TObjectIterator It; It; ++It) { UMaterialInstance* CurrentMaterialInstance = *It; // Only care about MI's with static parameters, because we are overriding parameter defaults, // And only MI's with static parameters contain uniform expressions, which contain parameter defaults if (CurrentMaterialInstance->bHasStaticPermutationResource) { UMaterial* BaseMaterial = CurrentMaterialInstance->GetMaterial(); if (MaterialsToOverride.Contains(BaseMaterial)) { CurrentMaterialInstance->OverrideVectorParameterDefault(ParameterName, Value, bOverride, FeatureLevel); } } } } void FMaterialEditor::OnVectorParameterDefaultChanged(class UMaterialExpression* Expression, FName ParameterName, const FLinearColor& Value) { check(Expression); if (Expression->Material == Material && OriginalMaterial) { SetVectorParameterDefaultOnDependentMaterials(ParameterName, Value, true); OverriddenVectorParametersToRevert.AddUnique(ParameterName); } } void FMaterialEditor::SetScalarParameterDefaultOnDependentMaterials(FName ParameterName, float Value, bool bOverride) { TArray MaterialsToOverride; if (MaterialFunction) { // Find all materials that reference this function for (TObjectIterator It; It; ++It) { UMaterial* CurrentMaterial = *It; if (CurrentMaterial != Material) { bool bUpdate = false; for (int32 FunctionIndex = 0; FunctionIndex < CurrentMaterial->MaterialFunctionInfos.Num(); FunctionIndex++) { if (CurrentMaterial->MaterialFunctionInfos[FunctionIndex].Function == MaterialFunction->ParentFunction) { bUpdate = true; break; } } if (bUpdate) { MaterialsToOverride.Add(CurrentMaterial); } } } } else { MaterialsToOverride.Add(OriginalMaterial); } const ERHIFeatureLevel::Type FeatureLevel = GEditor->GetEditorWorldContext().World()->FeatureLevel; for (int32 MaterialIndex = 0; MaterialIndex < MaterialsToOverride.Num(); MaterialIndex++) { UMaterial* CurrentMaterial = MaterialsToOverride[MaterialIndex]; CurrentMaterial->OverrideScalarParameterDefault(ParameterName, Value, bOverride, FeatureLevel); } // Update MI's that reference any of the materials affected for (TObjectIterator It; It; ++It) { UMaterialInstance* CurrentMaterialInstance = *It; // Only care about MI's with static parameters, because we are overriding parameter defaults, // And only MI's with static parameters contain uniform expressions, which contain parameter defaults if (CurrentMaterialInstance->bHasStaticPermutationResource) { UMaterial* BaseMaterial = CurrentMaterialInstance->GetMaterial(); if (MaterialsToOverride.Contains(BaseMaterial)) { CurrentMaterialInstance->OverrideScalarParameterDefault(ParameterName, Value, bOverride, FeatureLevel); } } } } void FMaterialEditor::OnScalarParameterDefaultChanged(class UMaterialExpression* Expression, FName ParameterName, float Value) { check(Expression); if (Expression->Material == Material && OriginalMaterial) { SetScalarParameterDefaultOnDependentMaterials(ParameterName, Value, true); OverriddenScalarParametersToRevert.AddUnique(ParameterName); } } TSharedRef FMaterialEditor::SpawnTab_Preview(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("ViewportTabTitle", "Viewport")) [ SNew( SOverlay ) + SOverlay::Slot() [ PreviewViewport.ToSharedRef() ] + SOverlay::Slot() [ PreviewUIViewport.ToSharedRef() ] ]; PreviewViewport->OnAddedToTab( SpawnedTab ); return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_GraphCanvas(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("GraphCanvasTitle", "Graph")); if (GraphEditor.IsValid()) { SpawnedTab->SetContent(GraphEditor.ToSharedRef()); } return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_MaterialProperties(const FSpawnTabArgs& Args) { TSharedPtr DetailsTab = SNew(SDockTab) .Icon( FEditorStyle::GetBrush("LevelEditor.Tabs.Details") ) .Label( LOCTEXT("MaterialDetailsTitle", "Details") ) [ MaterialDetailsView.ToSharedRef() ]; if (GraphEditor.IsValid()) { // Since we're initialising, make sure nothing is selected GraphEditor->ClearSelectionSet(); } SpawnedDetailsTab = DetailsTab; return DetailsTab.ToSharedRef(); } TSharedRef FMaterialEditor::SpawnTab_Palette(const FSpawnTabArgs& Args) { check( Args.GetTabId() == PaletteTabId ); TSharedRef SpawnedTab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("Kismet.Tabs.Palette")) .Label(LOCTEXT("MaterialPaletteTitle", "Palette")) [ SNew( SBox ) .AddMetaData(FTagMetaData(TEXT("MaterialPalette"))) [ Palette.ToSharedRef() ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_Find(const FSpawnTabArgs& Args) { check(Args.GetTabId() == FindTabId); TSharedRef SpawnedTab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("Kismet.Tabs.FindResults")) .Label(LOCTEXT("MaterialFindTitle", "Find Results")) [ SNew(SBox) .AddMetaData(FTagMetaData(TEXT("MaterialFind"))) [ FindResults.ToSharedRef() ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_PreviewSettings(const FSpawnTabArgs& Args) { check(Args.GetTabId() == PreviewSettingsTabId); TSharedRef InWidget = SNullWidget::NullWidget; if (PreviewViewport.IsValid()) { FAdvancedPreviewSceneModule& AdvancedPreviewSceneModule = FModuleManager::LoadModuleChecked("AdvancedPreviewScene"); InWidget = AdvancedPreviewSceneModule.CreateAdvancedPreviewSceneSettingsWidget(PreviewViewport->GetPreviewScene()); } TSharedRef SpawnedTab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Details")) .Label(LOCTEXT("PreviewSceneSettingsTab", "Preview Scene Settings")) [ SNew(SBox) [ InWidget ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_ParameterDefaults(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("LevelEditor.Tabs.Details")) .Label(LOCTEXT("ParameterDefaults", "Parameter Defaults")) [ SNew(SBox) [ MaterialParametersOverviewWidget.ToSharedRef() ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_LayerProperties(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Icon(FEditorStyle::GetBrush("MaterialInstanceEditor.Tabs.Properties")) .Label(LOCTEXT("MaterialLayerPropertiesTitle", "Layer Parameter Preview")) [ SNew(SBorder) .Padding(4) [ MaterialLayersFunctionsInstance.ToSharedRef() ] ]; return SpawnedTab; } void FMaterialEditor::SetPreviewExpression(UMaterialExpression* NewPreviewExpression) { UMaterialExpressionFunctionOutput* FunctionOutput = Cast(NewPreviewExpression); if (!NewPreviewExpression || PreviewExpression == NewPreviewExpression) { if (FunctionOutput) { FunctionOutput->bLastPreviewed = false; } // If we are already previewing the selected expression toggle previewing off PreviewExpression = NULL; ExpressionPreviewMaterial->Expressions.Empty(); SetPreviewMaterial( Material ); // Recompile the preview material to get changes that might have been made during previewing UpdatePreviewMaterial(); } else { if( ExpressionPreviewMaterial == NULL ) { // Create the expression preview material if it hasnt already been created ExpressionPreviewMaterial = NewObject(GetTransientPackage(), NAME_None, RF_Public); ExpressionPreviewMaterial->bIsPreviewMaterial = true; if (Material->IsUIMaterial()) { ExpressionPreviewMaterial->MaterialDomain = MD_UI; } else if (Material->IsPostProcessMaterial()) { ExpressionPreviewMaterial->MaterialDomain = MD_PostProcess; } } if (FunctionOutput) { FunctionOutput->bLastPreviewed = true; } else { //Hooking up the output of the break expression doesn't make much sense, preview the expression feeding it instead. UMaterialExpressionBreakMaterialAttributes* BreakExpr = Cast(NewPreviewExpression); if( BreakExpr && BreakExpr->GetInput(0) && BreakExpr->GetInput(0)->Expression ) { NewPreviewExpression = BreakExpr->GetInput(0)->Expression; } } // The expression preview material's expressions array must stay up to date before recompiling // So that RebuildMaterialFunctionInfo will see all the nested material functions that may need to be updated ExpressionPreviewMaterial->Expressions = Material->Expressions; // The preview window should now show the expression preview material SetPreviewMaterial( ExpressionPreviewMaterial ); // Set the preview expression PreviewExpression = NewPreviewExpression; // Recompile the preview material UpdatePreviewMaterial(); } } void FMaterialEditor::JumpToNode(const UEdGraphNode* Node) { GraphEditor->JumpToNode(Node, false); } UMaterialExpression* FMaterialEditor::CreateNewMaterialExpression(UClass* NewExpressionClass, const FVector2D& NodePos, bool bAutoSelect, bool bAutoAssignResource) { check( NewExpressionClass->IsChildOf(UMaterialExpression::StaticClass()) ); if (!IsAllowedExpressionType(NewExpressionClass, MaterialFunction != NULL)) { // Disallowed types should not be visible to the ui to be placed, so we don't need a warning here return NULL; } // Clear the selection if ( bAutoSelect ) { GraphEditor->ClearSelectionSet(); } // Create the new expression. UMaterialExpression* NewExpression = NULL; { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorNewExpression", "Material Editor: New Expression") ); Material->Modify(); UObject* SelectedAsset = nullptr; if (bAutoAssignResource) { // Load selected assets FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); SelectedAsset = GEditor->GetSelectedObjects()->GetTop(); } NewExpression = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, NewExpressionClass, SelectedAsset, NodePos.X, NodePos.Y); if (NewExpression) { Material->MaterialGraph->AddExpression(NewExpression, bAutoSelect); // Select the new node. if ( bAutoSelect ) { GraphEditor->SetNodeSelection(NewExpression->GraphNode, true); } } } RegenerateCodeView(); // Update the current preview material. UpdatePreviewMaterial(); Material->MarkPackageDirty(); RefreshExpressionPreviews(); GraphEditor->NotifyGraphChanged(); SetMaterialDirty(); MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance); return NewExpression; } UMaterialExpressionComment* FMaterialEditor::CreateNewMaterialExpressionComment(const FVector2D& NodePos) { UMaterialExpressionComment* NewComment = NULL; { const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MaterialEditorCreateComment", "Material Editor: Create comment")); Material->Modify(); UObject* ExpressionOuter = Material; if (MaterialFunction) { ExpressionOuter = MaterialFunction; } NewComment = NewObject(ExpressionOuter, NAME_None, RF_Transactional); // Add to the list of comments associated with this material. Material->EditorComments.Add( NewComment ); FSlateRect Bounds; if (GraphEditor->GetBoundsForSelectedNodes(Bounds, 50.0f)) { NewComment->MaterialExpressionEditorX = Bounds.Left; NewComment->MaterialExpressionEditorY = Bounds.Top; FVector2D Size = Bounds.GetSize(); NewComment->SizeX = Size.X; NewComment->SizeY = Size.Y; } else { NewComment->MaterialExpressionEditorX = NodePos.X; NewComment->MaterialExpressionEditorY = NodePos.Y; NewComment->SizeX = 400; NewComment->SizeY = 100; } NewComment->Text = NSLOCTEXT("K2Node", "CommentBlock_NewEmptyComment", "Comment").ToString(); Material->MaterialGraph->AddComment(NewComment, true); // Select the new comment. GraphEditor->ClearSelectionSet(); GraphEditor->SetNodeSelection(NewComment->GraphNode, true); } Material->MarkPackageDirty(); GraphEditor->NotifyGraphChanged(); SetMaterialDirty(); return NewComment; } void FMaterialEditor::ForceRefreshExpressionPreviews() { // Initialize expression previews. const bool bOldAlwaysRefreshAllPreviews = bAlwaysRefreshAllPreviews; bAlwaysRefreshAllPreviews = true; RefreshExpressionPreviews(); bAlwaysRefreshAllPreviews = bOldAlwaysRefreshAllPreviews; } void FMaterialEditor::AddToSelection(UMaterialExpression* Expression) { GraphEditor->SetNodeSelection(Expression->GraphNode, true); } void FMaterialEditor::SelectAllNodes() { GraphEditor->SelectAllNodes(); } bool FMaterialEditor::CanSelectAllNodes() const { return GraphEditor.IsValid(); } void FMaterialEditor::DeleteSelectedNodes() { TArray NodesToDelete; const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { NodesToDelete.Add(CastChecked(*NodeIt)); } DeleteNodes(NodesToDelete); } void FMaterialEditor::DeleteNodes(const TArray& NodesToDelete) { if (NodesToDelete.Num() > 0) { if (!CheckExpressionRemovalWarnings(NodesToDelete)) { return; } // If we are previewing an expression and the expression being previewed was deleted bool bHaveExpressionsToDelete = false; bool bPreviewExpressionDeleted = false; { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorDelete", "Material Editor: Delete") ); Material->Modify(); for (int32 Index = 0; Index < NodesToDelete.Num(); ++Index) { if (NodesToDelete[Index]->CanUserDeleteNode()) { // Break all node links first so that we don't update the material before deleting NodesToDelete[Index]->BreakAllNodeLinks(); FBlueprintEditorUtils::RemoveNode(NULL, NodesToDelete[Index], true); if (UMaterialGraphNode* GraphNode = Cast(NodesToDelete[Index])) { UMaterialExpression* MaterialExpression = GraphNode->MaterialExpression; bHaveExpressionsToDelete = true; DestroyColorPicker(); if( PreviewExpression == MaterialExpression ) { // The expression being previewed is also being deleted bPreviewExpressionDeleted = true; } MaterialExpression->Modify(); Material->Expressions.Remove( MaterialExpression ); Material->RemoveExpressionParameter(MaterialExpression); // Make sure the deleted expression is caught by gc MaterialExpression->MarkPendingKill(); } else if (UMaterialGraphNode_Comment* CommentNode = Cast(NodesToDelete[Index])) { CommentNode->MaterialExpressionComment->Modify(); Material->EditorComments.Remove( CommentNode->MaterialExpressionComment ); } } } Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); } // ScopedTransaction // Deselect all expressions and comments. GraphEditor->ClearSelectionSet(); GraphEditor->NotifyGraphChanged(); if ( bHaveExpressionsToDelete ) { if( bPreviewExpressionDeleted ) { // The preview expression was deleted. Null out our reference to it and reset to the normal preview material SetPreviewExpression(nullptr); } RegenerateCodeView(); } UpdatePreviewMaterial(); Material->MarkPackageDirty(); SetMaterialDirty(); if ( bHaveExpressionsToDelete ) { RefreshExpressionPreviews(); } } } bool FMaterialEditor::CanDeleteNodes() const { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); bool bDeletableNodeExists = false; for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UEdGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode && GraphNode->CanUserDeleteNode()) { bDeletableNodeExists = true; } } return SelectedNodes.Num() > 0 && bDeletableNodeExists; } void FMaterialEditor::DeleteSelectedDuplicatableNodes() { // Cache off the old selection const FGraphPanelSelectionSet OldSelectedNodes = GraphEditor->GetSelectedNodes(); // Clear the selection and only select the nodes that can be duplicated FGraphPanelSelectionSet RemainingNodes; GraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if ((Node != NULL) && Node->CanDuplicateNode()) { GraphEditor->SetNodeSelection(Node, true); } else { RemainingNodes.Add(Node); } } // Delete the duplicatable nodes DeleteSelectedNodes(); // Reselect whatever's left from the original selection after the deletion GraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(RemainingNodes); SelectedIter; ++SelectedIter) { if (UEdGraphNode* Node = Cast(*SelectedIter)) { GraphEditor->SetNodeSelection(Node, true); } } } void FMaterialEditor::CopySelectedNodes() { // Export the selected nodes and place the text on the clipboard const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); FString ExportedText; for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { if(UEdGraphNode* Node = Cast(*SelectedIter)) { Node->PrepareForCopying(); } } FEdGraphUtilities::ExportNodesToText(SelectedNodes, /*out*/ ExportedText); FPlatformApplicationMisc::ClipboardCopy(*ExportedText); // Make sure Material remains the owner of the copied nodes for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { if (UMaterialGraphNode* Node = Cast(*SelectedIter)) { Node->PostCopyNode(); } else if (UMaterialGraphNode_Comment* Comment = Cast(*SelectedIter)) { Comment->PostCopyNode(); } } } bool FMaterialEditor::CanCopyNodes() const { // If any of the nodes can be duplicated then we should allow copying const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if ((Node != NULL) && Node->CanDuplicateNode()) { return true; } } return false; } void FMaterialEditor::PasteNodes() { PasteNodesHere(GraphEditor->GetPasteLocation()); } void FMaterialEditor::PasteNodesHere(const FVector2D& Location) { // Undo/Redo support const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorPaste", "Material Editor: Paste") ); Material->MaterialGraph->Modify(); Material->Modify(); // Clear the selection set (newly pasted stuff will be selected) GraphEditor->ClearSelectionSet(); // Grab the text to paste from the clipboard. FString TextToImport; FPlatformApplicationMisc::ClipboardPaste(TextToImport); // Import the nodes TSet PastedNodes; FEdGraphUtilities::ImportNodesFromText(Material->MaterialGraph, TextToImport, /*out*/ PastedNodes); //Average position of nodes so we can move them while still maintaining relative distances to each other FVector2D AvgNodePosition(0.0f,0.0f); for (TSet::TIterator It(PastedNodes); It; ++It) { UEdGraphNode* Node = *It; AvgNodePosition.X += Node->NodePosX; AvgNodePosition.Y += Node->NodePosY; } if ( PastedNodes.Num() > 0 ) { float InvNumNodes = 1.0f/float(PastedNodes.Num()); AvgNodePosition.X *= InvNumNodes; AvgNodePosition.Y *= InvNumNodes; } for (TSet::TIterator It(PastedNodes); It; ++It) { UEdGraphNode* Node = *It; if (UMaterialGraphNode* GraphNode = Cast(Node)) { // These are not copied and we must account for expressions pasted between different materials anyway GraphNode->RealtimeDelegate = Material->MaterialGraph->RealtimeDelegate; GraphNode->MaterialDirtyDelegate = Material->MaterialGraph->MaterialDirtyDelegate; GraphNode->bPreviewNeedsUpdate = false; UMaterialExpression* NewExpression = GraphNode->MaterialExpression; NewExpression->Material = Material; NewExpression->Function = MaterialFunction; // Make sure the param name is valid after the paste if (NewExpression->HasAParameterName()) { NewExpression->ValidateParameterName(); } Material->Expressions.Add(NewExpression); // There can be only one default mesh paint texture. UMaterialExpressionTextureBase* TextureSample = Cast( NewExpression ); if( TextureSample ) { TextureSample->IsDefaultMeshpaintTexture = false; } NewExpression->UpdateParameterGuid(true, true); Material->AddExpressionParameter(NewExpression, Material->EditorParameters); UMaterialExpressionFunctionInput* FunctionInput = Cast( NewExpression ); if( FunctionInput ) { FunctionInput->ConditionallyGenerateId(true); FunctionInput->ValidateName(); } UMaterialExpressionFunctionOutput* FunctionOutput = Cast( NewExpression ); if( FunctionOutput ) { FunctionOutput->ConditionallyGenerateId(true); FunctionOutput->ValidateName(); } UMaterialExpressionMaterialFunctionCall* FunctionCall = Cast( NewExpression ); if( FunctionCall ) { // When pasting new nodes, we don't want to break all node links as this information is used by UpdateMaterialAfterGraphChange() below, // to recreate all the connections in the pasted group. // Just update the function input/outputs here. const bool bRecreateAndLinkNode = false; FunctionCall->UpdateFromFunctionResource(bRecreateAndLinkNode); // If an unknown material function has been pasted, remove the graph node pins (as the expression will also have had its inputs/outputs removed). // This will be displayed as an orphaned "Unspecified Function" node. if (FunctionCall->MaterialFunction == nullptr && FunctionCall->FunctionInputs.Num() == 0 && FunctionCall->FunctionOutputs.Num() == 0) { GraphNode->Pins.Empty(); } } } else if (UMaterialGraphNode_Comment* CommentNode = Cast(Node)) { if (CommentNode->MaterialExpressionComment) { CommentNode->MaterialDirtyDelegate = Material->MaterialGraph->MaterialDirtyDelegate; CommentNode->MaterialExpressionComment->Material = Material; CommentNode->MaterialExpressionComment->Function = MaterialFunction; Material->EditorComments.Add(CommentNode->MaterialExpressionComment); } } // Select the newly pasted stuff GraphEditor->SetNodeSelection(Node, true); Node->NodePosX = (Node->NodePosX - AvgNodePosition.X) + Location.X ; Node->NodePosY = (Node->NodePosY - AvgNodePosition.Y) + Location.Y ; Node->SnapToGrid(SNodePanel::GetSnapGridSize()); // Give new node a different Guid from the old one Node->CreateNewGuid(); } UpdateMaterialAfterGraphChange(); // Update UI GraphEditor->NotifyGraphChanged(); } bool FMaterialEditor::CanPasteNodes() const { FString ClipboardContent; FPlatformApplicationMisc::ClipboardPaste(ClipboardContent); return FEdGraphUtilities::CanImportNodesFromText(Material->MaterialGraph, ClipboardContent); } void FMaterialEditor::CutSelectedNodes() { CopySelectedNodes(); // Cut should only delete nodes that can be duplicated DeleteSelectedDuplicatableNodes(); } bool FMaterialEditor::CanCutNodes() const { return CanCopyNodes() && CanDeleteNodes(); } void FMaterialEditor::DuplicateNodes() { // Copy and paste current selection CopySelectedNodes(); PasteNodes(); } bool FMaterialEditor::CanDuplicateNodes() const { return CanCopyNodes(); } FText FMaterialEditor::GetOriginalObjectName() const { return FText::FromString(GetEditingObjects()[0]->GetName()); } void FMaterialEditor::UpdateMaterialAfterGraphChange() { FlushRenderingCommands(); Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); // Update the current preview material. UpdatePreviewMaterial(); Material->MarkPackageDirty(); RegenerateCodeView(); RefreshExpressionPreviews(); SetMaterialDirty(); } int32 FMaterialEditor::GetNumberOfSelectedNodes() const { return GraphEditor->GetSelectedNodes().Num(); } FMaterialRenderProxy* FMaterialEditor::GetExpressionPreview(UMaterialExpression* InExpression) { bool bNewlyCreated; return GetExpressionPreview(InExpression, bNewlyCreated); } void FMaterialEditor::UndoGraphAction() { FlushRenderingCommands(); int32 NumExpressions = Material->Expressions.Num(); GEditor->UndoTransaction(); if(NumExpressions != Material->Expressions.Num()) { Material->BuildEditorParameterList(); } } void FMaterialEditor::RedoGraphAction() { FlushRenderingCommands(); // Clear selection, to avoid holding refs to nodes that go away GraphEditor->ClearSelectionSet(); int32 NumExpressions = Material->Expressions.Num(); GEditor->RedoTransaction(); if(NumExpressions != Material->Expressions.Num()) { Material->BuildEditorParameterList(); } } void FMaterialEditor::OnAlignTop() { if (GraphEditor.IsValid()) { GraphEditor->OnAlignTop(); } } void FMaterialEditor::OnAlignMiddle() { if (GraphEditor.IsValid()) { GraphEditor->OnAlignMiddle(); } } void FMaterialEditor::OnAlignBottom() { if (GraphEditor.IsValid()) { GraphEditor->OnAlignMiddle(); } } void FMaterialEditor::OnAlignLeft() { if (GraphEditor.IsValid()) { GraphEditor->OnAlignLeft(); } } void FMaterialEditor::OnAlignCenter() { if (GraphEditor.IsValid()) { GraphEditor->OnAlignCenter(); } } void FMaterialEditor::OnAlignRight() { if (GraphEditor.IsValid()) { GraphEditor->OnAlignRight(); } } void FMaterialEditor::OnStraightenConnections() { if (GraphEditor.IsValid()) { GraphEditor->OnStraightenConnections(); } } void FMaterialEditor::OnDistributeNodesH() { if (GraphEditor.IsValid()) { GraphEditor->OnDistributeNodesH(); } } void FMaterialEditor::OnDistributeNodesV() { if (GraphEditor.IsValid()) { GraphEditor->OnDistributeNodesV(); } } void FMaterialEditor::PostUndo(bool bSuccess) { if (bSuccess) { GraphEditor->ClearSelectionSet(); Material->BuildEditorParameterList(); // Update the current preview material. UpdatePreviewMaterial(); UpdatePreviewViewportsVisibility(); RefreshExpressionPreviews(); GraphEditor->NotifyGraphChanged(); SetMaterialDirty(); } } void FMaterialEditor::NotifyPreChange(UProperty* PropertyAboutToChange) { check( !ScopedTransaction ); ScopedTransaction = new FScopedTransaction( NSLOCTEXT("UnrealEd", "MaterialEditorEditProperties", "Material Editor: Edit Properties") ); FlushRenderingCommands(); } void FMaterialEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged) { check( ScopedTransaction ); if ( PropertyThatChanged ) { if (MaterialLayersFunctionsInstance.IsValid()) { MaterialLayersFunctionsInstance->SetEditorInstance(MaterialEditorInstance); } MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance); const FName NameOfPropertyThatChanged( *PropertyThatChanged->GetName() ); if ((NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterialInterface, PreviewMesh)) || (NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterial, bUsedWithSkeletalMesh))) { // SetPreviewMesh will return false if the material has bUsedWithSkeletalMesh and // a skeleton was requested, in which case revert to a sphere static mesh. if (!SetPreviewAssetByName(*Material->PreviewMesh.ToString())) { SetPreviewAsset(GUnrealEd->GetThumbnailManager()->EditorSphere); } } else if( NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterial, MaterialDomain) || NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterial, ShadingModel)) { Material->MaterialGraph->RebuildGraph(); TArray> SelectedObjects = MaterialDetailsView->GetSelectedObjects(); MaterialDetailsView->SetObjects( SelectedObjects, true ); if (ExpressionPreviewMaterial) { if (Material->IsUIMaterial()) { ExpressionPreviewMaterial->MaterialDomain = MD_UI; } else { ExpressionPreviewMaterial->MaterialDomain = MD_Surface; } SetPreviewMaterial(ExpressionPreviewMaterial); } UpdatePreviewViewportsVisibility(); } FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* SelectedNode = Cast(*NodeIt); if (SelectedNode && SelectedNode->MaterialExpression) { if(NameOfPropertyThatChanged == FName(TEXT("ParameterName"))) { Material->UpdateExpressionParameterName(SelectedNode->MaterialExpression); } else if (SelectedNode->MaterialExpression->IsA(UMaterialExpressionDynamicParameter::StaticClass())) { Material->UpdateExpressionDynamicParameters(SelectedNode->MaterialExpression); } else { Material->PropagateExpressionParameterChanges(SelectedNode->MaterialExpression); } } } } // Prevent constant recompilation of materials while properties are being interacted with if( PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive ) { // Also prevent recompilation when properties have no effect on material output const FName PropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : NAME_None; if (PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionComment, Text) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionComment, CommentColor) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpression, Desc)) { // Update the current preview material. UpdatePreviewMaterial(); RefreshExpressionPreviews(); RegenerateCodeView(); } GetDefault()->ForceVisualizationCacheClear(); } delete ScopedTransaction; ScopedTransaction = NULL; Material->MarkPackageDirty(); SetMaterialDirty(); } void FMaterialEditor::ToggleCollapsed(UMaterialExpression* MaterialExpression) { check( MaterialExpression ); { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorToggleCollapsed", "Material Editor: Toggle Collapsed") ); MaterialExpression->Modify(); MaterialExpression->bCollapsed = !MaterialExpression->bCollapsed; } MaterialExpression->PreEditChange( NULL ); MaterialExpression->PostEditChange(); MaterialExpression->MarkPackageDirty(); SetMaterialDirty(); // Update the preview. RefreshExpressionPreview( MaterialExpression, true ); RefreshPreviewViewport(); } void FMaterialEditor::RefreshExpressionPreviews(bool bForceRefreshAll /*= false*/) { const FScopedBusyCursor BusyCursor; if ( bAlwaysRefreshAllPreviews || bForceRefreshAll) { // we need to make sure the rendering thread isn't drawing these tiles SCOPED_SUSPEND_RENDERING_THREAD(true); // Refresh all expression previews. ExpressionPreviews.Empty(); for (int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ++ExpressionIndex) { UMaterialExpression* MaterialExpression = Material->Expressions[ExpressionIndex]; UMaterialGraphNode* GraphNode = Cast(MaterialExpression->GraphNode); if (GraphNode) { GraphNode->InvalidatePreviewMaterialDelegate.ExecuteIfBound(); } } } else { // Only refresh expressions that are marked for realtime update. for ( int32 ExpressionIndex = 0 ; ExpressionIndex < Material->Expressions.Num() ; ++ExpressionIndex ) { UMaterialExpression* MaterialExpression = Material->Expressions[ ExpressionIndex ]; RefreshExpressionPreview( MaterialExpression, false ); } } TArray ExpressionPreviewsBeingCompiled; ExpressionPreviewsBeingCompiled.Empty(50); // Go through all expression previews and create new ones as needed, and maintain a list of previews that are being compiled for( int32 ExpressionIndex = 0; ExpressionIndex < Material->Expressions.Num(); ++ExpressionIndex ) { UMaterialExpression* MaterialExpression = Material->Expressions[ ExpressionIndex ]; if (MaterialExpression && !MaterialExpression->IsA(UMaterialExpressionComment::StaticClass()) ) { bool bNewlyCreated; FMatExpressionPreview* Preview = GetExpressionPreview( MaterialExpression, bNewlyCreated ); if (bNewlyCreated && Preview) { ExpressionPreviewsBeingCompiled.Add(Preview); } } } } void FMaterialEditor::RefreshExpressionPreview(UMaterialExpression* MaterialExpression, bool bRecompile) { if ( (MaterialExpression->bRealtimePreview || MaterialExpression->bNeedToUpdatePreview) && !MaterialExpression->bCollapsed ) { for( int32 PreviewIndex = 0 ; PreviewIndex < ExpressionPreviews.Num() ; ++PreviewIndex ) { FMatExpressionPreview& ExpressionPreview = ExpressionPreviews[PreviewIndex]; if( ExpressionPreview.GetExpression() == MaterialExpression ) { // we need to make sure the rendering thread isn't drawing this tile SCOPED_SUSPEND_RENDERING_THREAD(true); ExpressionPreviews.RemoveAt( PreviewIndex ); MaterialExpression->bNeedToUpdatePreview = false; if (bRecompile) { bool bNewlyCreated; GetExpressionPreview(MaterialExpression, bNewlyCreated); } UMaterialGraphNode* GraphNode = Cast(MaterialExpression->GraphNode); if (GraphNode) { GraphNode->InvalidatePreviewMaterialDelegate.ExecuteIfBound(); } break; } } } } FMatExpressionPreview* FMaterialEditor::GetExpressionPreview(UMaterialExpression* MaterialExpression, bool& bNewlyCreated) { bNewlyCreated = false; if (!MaterialExpression->bHidePreviewWindow && !MaterialExpression->bCollapsed) { FMatExpressionPreview* Preview = NULL; for( int32 PreviewIndex = 0 ; PreviewIndex < ExpressionPreviews.Num() ; ++PreviewIndex ) { FMatExpressionPreview& ExpressionPreview = ExpressionPreviews[PreviewIndex]; if( ExpressionPreview.GetExpression() == MaterialExpression ) { Preview = &ExpressionPreviews[PreviewIndex]; break; } } if( !Preview ) { bNewlyCreated = true; Preview = new FMatExpressionPreview(MaterialExpression); ExpressionPreviews.Add(Preview); Preview->CacheShaders(GMaxRHIShaderPlatform, true); } return Preview; } return NULL; } void FMaterialEditor::PreColorPickerCommit(FLinearColor LinearColor) { // Begin a property edit transaction. if ( GEditor ) { GEditor->BeginTransaction( LOCTEXT("ModifyColorPicker", "Modify Color Picker Value") ); } NotifyPreChange(NULL); UObject* Object = ColorPickerObject.Get(false); if( Object ) { Object->PreEditChange(NULL); } } void FMaterialEditor::OnColorPickerCommitted(FLinearColor LinearColor) { UObject* Object = ColorPickerObject.Get(false); if( Object ) { Object->MarkPackageDirty(); FPropertyChangedEvent Event(ColorPickerProperty.Get(false)); Object->PostEditChangeProperty(Event); } NotifyPostChange(NULL,NULL); if ( GEditor ) { GEditor->EndTransaction(); } RefreshExpressionPreviews(); MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance); } TSharedRef FMaterialEditor::CreateGraphEditorWidget() { GraphEditorCommands = MakeShareable( new FUICommandList ); { // Editing commands GraphEditorCommands->MapAction( FGenericCommands::Get().SelectAll, FExecuteAction::CreateSP( this, &FMaterialEditor::SelectAllNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanSelectAllNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Delete, FExecuteAction::CreateSP( this, &FMaterialEditor::DeleteSelectedNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanDeleteNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Copy, FExecuteAction::CreateSP( this, &FMaterialEditor::CopySelectedNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanCopyNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Paste, FExecuteAction::CreateSP( this, &FMaterialEditor::PasteNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanPasteNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Cut, FExecuteAction::CreateSP( this, &FMaterialEditor::CutSelectedNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanCutNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP( this, &FMaterialEditor::DuplicateNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanDuplicateNodes ) ); // Graph Editor Commands GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CreateComment, FExecuteAction::CreateSP( this, &FMaterialEditor::OnCreateComment ) ); // Material specific commands GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().UseCurrentTexture, FExecuteAction::CreateSP(this, &FMaterialEditor::OnUseCurrentTexture) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertObjects, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertToTextureObjects, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertToTextureSamples, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertTextures) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertToConstant, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertObjects) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().StopPreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().StartPreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPreviewNode) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().EnableRealtimePreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().DisableRealtimePreviewNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnToggleRealtimePreview) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().SelectDownstreamNodes, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectDownstreamNodes) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().SelectUpstreamNodes, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectUpstreamNodes) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().RemoveFromFavorites, FExecuteAction::CreateSP(this, &FMaterialEditor::RemoveSelectedExpressionFromFavorites) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().AddToFavorites, FExecuteAction::CreateSP(this, &FMaterialEditor::AddSelectedExpressionToFavorites) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ForceRefreshPreviews, FExecuteAction::CreateSP(this, &FMaterialEditor::OnForceRefreshPreviews) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().CreateComponentMaskNode, FExecuteAction::CreateSP(this, &FMaterialEditor::OnCreateComponentMaskNode) ); GraphEditorCommands->MapAction(FGraphEditorCommands::Get().GoToDocumentation, FExecuteAction::CreateSP(this, &FMaterialEditor::OnGoToDocumentation), FCanExecuteAction::CreateSP(this, &FMaterialEditor::CanGoToDocumentation) ); GraphEditorCommands->MapAction(FMaterialEditorCommands::Get().PromoteToParameter, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteToParameter), FCanExecuteAction::CreateSP(this, &FMaterialEditor::OnCanPromoteToParameter) ); // Alignment Commands GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesTop, FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignTop) ); GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesMiddle, FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignMiddle) ); GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesBottom, FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignBottom) ); GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesLeft, FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignLeft) ); GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesCenter, FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignCenter) ); GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesRight, FExecuteAction::CreateSP(this, &FMaterialEditor::OnAlignRight) ); GraphEditorCommands->MapAction(FGraphEditorCommands::Get().StraightenConnections, FExecuteAction::CreateSP(this, &FMaterialEditor::OnStraightenConnections) ); // Distribution Commands GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DistributeNodesHorizontally, FExecuteAction::CreateSP(this, &FMaterialEditor::OnDistributeNodesH) ); GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DistributeNodesVertically, FExecuteAction::CreateSP(this, &FMaterialEditor::OnDistributeNodesV) ); } SGraphEditor::FGraphEditorEvents InEvents; InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FMaterialEditor::OnSelectedNodesChanged); InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FMaterialEditor::OnNodeDoubleClicked); InEvents.OnTextCommitted = FOnNodeTextCommitted::CreateSP(this, &FMaterialEditor::OnNodeTitleCommitted); InEvents.OnVerifyTextCommit = FOnNodeVerifyTextCommit::CreateSP(this, &FMaterialEditor::OnVerifyNodeTextCommit); InEvents.OnSpawnNodeByShortcut = SGraphEditor::FOnSpawnNodeByShortcut::CreateSP(this, &FMaterialEditor::OnSpawnGraphNodeByShortcut, static_cast(Material->MaterialGraph)); // Create the title bar widget TSharedPtr TitleBarWidget = SNew(SMaterialEditorTitleBar) .TitleText(this, &FMaterialEditor::GetOriginalObjectName) .MaterialInfoList(&MaterialInfoList); return SNew(SGraphEditor) .AdditionalCommands(GraphEditorCommands) .IsEditable(true) .TitleBar(TitleBarWidget) .Appearance(this, &FMaterialEditor::GetGraphAppearance) .GraphToEdit(Material->MaterialGraph) .GraphEvents(InEvents) .ShowGraphStateOverlay(false); } FGraphAppearanceInfo FMaterialEditor::GetGraphAppearance() const { FGraphAppearanceInfo AppearanceInfo; if (MaterialFunction) { switch (MaterialFunction->GetMaterialFunctionUsage()) { case EMaterialFunctionUsage::MaterialLayer: AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_MaterialLayer", "MATERIAL LAYER"); break; case EMaterialFunctionUsage::MaterialLayerBlend: AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_MaterialLayerBlend", "MATERIAL LAYER BLEND"); break; default: AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_MaterialFunction", "MATERIAL FUNCTION"); } } else { AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_Material", "MATERIAL"); } return AppearanceInfo; } void FMaterialEditor::CleanUnusedExpressions() { TArray UnusedNodes; Material->MaterialGraph->GetUnusedExpressions(UnusedNodes); if (UnusedNodes.Num() > 0 && CheckExpressionRemovalWarnings(UnusedNodes)) { { // Kill off expressions referenced by the material that aren't reachable. const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorCleanUnusedExpressions", "Material Editor: Clean Unused Expressions") ); Material->Modify(); Material->MaterialGraph->Modify(); for (int32 Index = 0; Index < UnusedNodes.Num(); ++Index) { UMaterialGraphNode* GraphNode = CastChecked(UnusedNodes[Index]); UMaterialExpression* MaterialExpression = GraphNode->MaterialExpression; FBlueprintEditorUtils::RemoveNode(NULL, GraphNode, true); if (PreviewExpression == MaterialExpression) { SetPreviewExpression(NULL); } MaterialExpression->Modify(); Material->Expressions.Remove(MaterialExpression); Material->RemoveExpressionParameter(MaterialExpression); // Make sure the deleted expression is caught by gc MaterialExpression->MarkPendingKill(); } Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); } // ScopedTransaction GraphEditor->ClearSelectionSet(); GraphEditor->NotifyGraphChanged(); SetMaterialDirty(); } } bool FMaterialEditor::CheckExpressionRemovalWarnings(const TArray& NodesToRemove) { FString FunctionWarningString; bool bFirstExpression = true; for (int32 Index = 0; Index < NodesToRemove.Num(); ++Index) { if (UMaterialGraphNode* GraphNode = Cast(NodesToRemove[Index])) { UMaterialExpressionFunctionInput* FunctionInput = Cast(GraphNode->MaterialExpression); UMaterialExpressionFunctionOutput* FunctionOutput = Cast(GraphNode->MaterialExpression); if (FunctionInput) { if (!bFirstExpression) { FunctionWarningString += TEXT(", "); } bFirstExpression = false; FunctionWarningString += FunctionInput->InputName.ToString(); } if (FunctionOutput && MaterialFunction && MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::Default) { if (!bFirstExpression) { FunctionWarningString += TEXT(", "); } bFirstExpression = false; FunctionWarningString += FunctionOutput->OutputName.ToString(); } } } if (FunctionWarningString.Len() > 0) { if (EAppReturnType::Yes != FMessageDialog::Open( EAppMsgType::YesNo, FText::Format( NSLOCTEXT("UnrealEd", "Prompt_MaterialEditorDeleteFunctionInputs", "Delete function inputs or outputs \"{0}\"?\nAny materials which use this function will lose their connections to these function inputs or outputs once deleted."), FText::FromString(FunctionWarningString) ))) { // User said don't delete return false; } } return true; } void FMaterialEditor::RemoveSelectedExpressionFromFavorites() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UMaterialGraphNode* GraphNode = Cast(*NodeIt)) { MaterialExpressionClasses::Get()->RemoveMaterialExpressionFromFavorites(GraphNode->MaterialExpression->GetClass()); EditorOptions->FavoriteExpressions.Remove(GraphNode->MaterialExpression->GetClass()->GetName()); EditorOptions->SaveConfig(); } } } } void FMaterialEditor::AddSelectedExpressionToFavorites() { const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UMaterialGraphNode* GraphNode = Cast(*NodeIt)) { MaterialExpressionClasses::Get()->AddMaterialExpressionToFavorites(GraphNode->MaterialExpression->GetClass()); EditorOptions->FavoriteExpressions.AddUnique(GraphNode->MaterialExpression->GetClass()->GetName()); EditorOptions->SaveConfig(); } } } } void FMaterialEditor::OnSelectedNodesChanged(const TSet& NewSelection) { TArray SelectedObjects; UObject* EditObject = Material; if (MaterialFunction) { EditObject = MaterialFunction; } if( NewSelection.Num() == 0 ) { SelectedObjects.Add(EditObject); } else { for(TSet::TConstIterator SetIt(NewSelection);SetIt;++SetIt) { if (UMaterialGraphNode* GraphNode = Cast(*SetIt)) { SelectedObjects.Add(GraphNode->MaterialExpression); } else if (UMaterialGraphNode_Comment* CommentNode = Cast(*SetIt)) { SelectedObjects.Add(CommentNode->MaterialExpressionComment); } else { SelectedObjects.Add(EditObject); } } } GetDetailView()->SetObjects( SelectedObjects, true ); FocusDetailsPanel(); } void FMaterialEditor::OnNodeDoubleClicked(class UEdGraphNode* Node) { UMaterialGraphNode* GraphNode = Cast(Node); if (GraphNode) { UMaterialExpressionConstant3Vector* Constant3Expression = Cast(GraphNode->MaterialExpression); UMaterialExpressionConstant4Vector* Constant4Expression = Cast(GraphNode->MaterialExpression); UMaterialExpressionFunctionInput* InputExpression = Cast(GraphNode->MaterialExpression); UMaterialExpressionVectorParameter* VectorExpression = Cast(GraphNode->MaterialExpression); FColorChannels ChannelEditStruct; // Reset to default ColorPickerProperty = NULL; if( Constant3Expression ) { ChannelEditStruct.Red = &Constant3Expression->Constant.R; ChannelEditStruct.Green = &Constant3Expression->Constant.G; ChannelEditStruct.Blue = &Constant3Expression->Constant.B; } else if( Constant4Expression ) { ChannelEditStruct.Red = &Constant4Expression->Constant.R; ChannelEditStruct.Green = &Constant4Expression->Constant.G; ChannelEditStruct.Blue = &Constant4Expression->Constant.B; ChannelEditStruct.Alpha = &Constant4Expression->Constant.A; } else if (InputExpression) { ChannelEditStruct.Red = &InputExpression->PreviewValue.X; ChannelEditStruct.Green = &InputExpression->PreviewValue.Y; ChannelEditStruct.Blue = &InputExpression->PreviewValue.Z; ChannelEditStruct.Alpha = &InputExpression->PreviewValue.W; } else if (VectorExpression) { ChannelEditStruct.Red = &VectorExpression->DefaultValue.R; ChannelEditStruct.Green = &VectorExpression->DefaultValue.G; ChannelEditStruct.Blue = &VectorExpression->DefaultValue.B; ChannelEditStruct.Alpha = &VectorExpression->DefaultValue.A; static FName DefaultValueName = FName(TEXT("DefaultValue")); // Store off the property the color picker will be manipulating, so we can construct a useful PostEditChangeProperty later ColorPickerProperty = VectorExpression->GetClass()->FindPropertyByName(DefaultValueName); } if (ChannelEditStruct.Red || ChannelEditStruct.Green || ChannelEditStruct.Blue || ChannelEditStruct.Alpha) { TArray Channels; Channels.Add(ChannelEditStruct); ColorPickerObject = GraphNode->MaterialExpression; // Open a color picker FColorPickerArgs PickerArgs; PickerArgs.ParentWidget = GraphEditor;//AsShared(); PickerArgs.bUseAlpha = ChannelEditStruct.Alpha != nullptr; PickerArgs.bOnlyRefreshOnOk = false; PickerArgs.bOnlyRefreshOnMouseUp = true; PickerArgs.bExpandAdvancedSection = true; PickerArgs.DisplayGamma = TAttribute::Create( TAttribute::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma) ); PickerArgs.ColorChannelsArray = &Channels; PickerArgs.OnColorCommitted = FOnLinearColorValueChanged::CreateSP(this, &FMaterialEditor::OnColorPickerCommitted); PickerArgs.PreColorCommitted = FOnLinearColorValueChanged::CreateSP(this, &FMaterialEditor::PreColorPickerCommit); PickerArgs.OptionalOwningDetailsView = MaterialDetailsView; OpenColorPicker(PickerArgs); } UMaterialExpressionTextureSample* TextureExpression = Cast(GraphNode->MaterialExpression); UMaterialExpressionTextureSampleParameter* TextureParameterExpression = Cast(GraphNode->MaterialExpression); UMaterialExpressionMaterialFunctionCall* FunctionExpression = Cast(GraphNode->MaterialExpression); UMaterialExpressionCollectionParameter* CollectionParameter = Cast(GraphNode->MaterialExpression); TArray ObjectsToView; UObject* ObjectToEdit = NULL; if (TextureExpression && TextureExpression->Texture) { ObjectsToView.Add(TextureExpression->Texture); } else if (TextureParameterExpression && TextureParameterExpression->Texture) { ObjectsToView.Add(TextureParameterExpression->Texture); } else if (FunctionExpression && FunctionExpression->MaterialFunction) { ObjectToEdit = FunctionExpression->MaterialFunction; } else if (CollectionParameter && CollectionParameter->Collection) { ObjectToEdit = CollectionParameter->Collection; } if (ObjectsToView.Num() > 0) { GEditor->SyncBrowserToObjects(ObjectsToView); } if (ObjectToEdit) { FAssetEditorManager::Get().OpenEditorForAsset(ObjectToEdit); } } } void FMaterialEditor::OnNodeTitleCommitted(const FText& NewText, ETextCommit::Type CommitInfo, UEdGraphNode* NodeBeingChanged) { if (NodeBeingChanged) { const FScopedTransaction Transaction( LOCTEXT( "RenameNode", "Rename Node" ) ); NodeBeingChanged->Modify(); NodeBeingChanged->OnRenameNode(NewText.ToString()); MaterialParametersOverviewWidget->UpdateEditorInstance(MaterialEditorInstance); } } bool FMaterialEditor::OnVerifyNodeTextCommit(const FText& NewText, UEdGraphNode* NodeBeingChanged, FText& OutErrorMessage) { bool bValid = true; UMaterialGraphNode* MaterialNode = Cast(NodeBeingChanged); if( MaterialNode && MaterialNode->MaterialExpression && MaterialNode->MaterialExpression->IsA() ) { if( NewText.ToString().Len() > NAME_SIZE ) { OutErrorMessage = FText::Format( LOCTEXT("MaterialEditorExpressionError_NameTooLong", "Parameter names must be less than {0} characters"), FText::AsNumber(NAME_SIZE)); bValid = false; } } return bValid; } FReply FMaterialEditor::OnSpawnGraphNodeByShortcut(FInputChord InChord, const FVector2D& InPosition, UEdGraph* InGraph) { UEdGraph* Graph = InGraph; TSharedPtr< FEdGraphSchemaAction > Action = FMaterialEditorSpawnNodeCommands::Get().GetGraphActionByChord(InChord, InGraph); if(Action.IsValid()) { TArray DummyPins; Action->PerformAction(Graph, DummyPins, InPosition); return FReply::Handled(); } return FReply::Unhandled(); } void FMaterialEditor::UpdateStatsMaterials() { if (bShowBuiltinStats && bStatsFromPreviewMaterial) { UMaterial* StatsMaterial = Material; FString EmptyMaterialName = FString(TEXT("MEStatsMaterial_Empty_")) + Material->GetName(); EmptyMaterial = (UMaterial*)StaticDuplicateObject(Material, GetTransientPackage(), *EmptyMaterialName, ~RF_Standalone, UPreviewMaterial::StaticClass()); EmptyMaterial->Expressions.Empty(); //Disconnect all properties from the expressions for (int32 PropIdx = 0; PropIdx < MP_MAX; ++PropIdx) { FExpressionInput* ExpInput = EmptyMaterial->GetExpressionInputForProperty((EMaterialProperty)PropIdx); if(ExpInput) { ExpInput->Expression = NULL; } } EmptyMaterial->bAllowDevelopmentShaderCompile = Material->bAllowDevelopmentShaderCompile; EmptyMaterial->PreEditChange(NULL); EmptyMaterial->PostEditChange(); } } void FMaterialEditor::NotifyExternalMaterialChange() { MaterialStatsManager->SignalMaterialChanged(); } void FMaterialEditor::FocusDetailsPanel() { if (SpawnedDetailsTab.IsValid() && !SpawnedDetailsTab.Pin()->IsForeground()) { SpawnedDetailsTab.Pin()->DrawAttention(); } } #undef LOCTEXT_NAMESPACE