// Copyright 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 "EngineModule.h" #include "Misc/MessageDialog.h" #include "Misc/UObjectToken.h" #include "Misc/TransactionObjectEvent.h" #include "Modules/ModuleManager.h" #include "SlateOptMacros.h" #include "Widgets/Layout/SSeparator.h" #include "Widgets/Layout/SBox.h" #include "Widgets/Input/SButton.h" #include "Widgets/Input/SNumericEntryBox.h" #include "Widgets/Input/SCheckBox.h" #include "Styling/AppStyle.h" #include "EdGraph/EdGraph.h" #include "WorkflowOrientedApp/WorkflowUObjectDocuments.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/MaterialGraphNode_Composite.h" #include "MaterialGraph/MaterialGraphNode_PinBase.h" #include "MaterialGraph/MaterialGraphSchema.h" #include "MaterialEditor/PreviewMaterial.h" #include "ThumbnailRendering/SceneThumbnailInfoWithPrimitive.h" #include "Particles/ParticleSystemComponent.h" #include "Materials/MaterialAttributeDefinitionMap.h" #include "Materials/MaterialInstance.h" #include "Materials/MaterialInstanceDynamic.h" #include "Materials/MaterialInstanceConstant.h" #include "Materials/MaterialParameterCollection.h" #include "Materials/MaterialParameterCollectionInstance.h" #include "StaticParameterSet.h" #include "Engine/TextureCube.h" #include "Engine/Texture2DArray.h" #include "Engine/TextureCubeArray.h" #include "Dialogs/Dialogs.h" #include "UnrealEdGlobals.h" #include "Editor.h" #include "MaterialEditorModule.h" #include "MaterialEditingLibrary.h" #include "HAL/PlatformApplicationMisc.h" #include "MaterialCachedData.h" #include "DataDrivenShaderPlatformInfo.h" #include "Materials/MaterialExpressionBreakMaterialAttributes.h" #include "Materials/MaterialExpressionCollectionParameter.h" #include "Materials/MaterialExpressionComment.h" #include "Materials/MaterialExpressionComposite.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/MaterialExpressionPinBase.h" #include "Materials/MaterialExpressionTextureBase.h" #include "Materials/MaterialExpressionTextureSample.h" #include "Materials/MaterialExpressionParticleSubUV.h" #include "Materials/MaterialExpressionReroute.h" #include "Materials/MaterialExpressionRuntimeVirtualTextureSample.h" #include "Materials/MaterialExpressionRuntimeVirtualTextureSampleParameter.h" #include "Materials/MaterialExpressionSparseVolumeTextureSample.h" #include "Materials/MaterialExpressionScalarParameter.h" #include "Materials/MaterialExpressionStaticComponentMaskParameter.h" #include "Materials/MaterialExpressionStaticSwitchParameter.h" #include "Materials/MaterialExpressionTextureSampleParameter.h" #include "Materials/MaterialExpressionTextureObjectParameter.h" #include "Materials/MaterialExpressionTextureObject.h" #include "Materials/MaterialExpressionTextureSampleParameter2D.h" #include "Materials/MaterialExpressionTextureSampleParameterCube.h" #include "Materials/MaterialExpressionTextureSampleParameter2DArray.h" #include "Materials/MaterialExpressionTextureSampleParameterCubeArray.h" #include "Materials/MaterialExpressionTextureSampleParameterSubUV.h" #include "Materials/MaterialExpressionTransformPosition.h" #include "Materials/MaterialExpressionVectorParameter.h" #include "Materials/MaterialExpressionDoubleVectorParameter.h" #include "Materials/MaterialExpressionStaticBoolParameter.h" #include "Materials/MaterialExpressionCustomOutput.h" #include "Materials/MaterialFunction.h" #include "Materials/MaterialFunctionInstance.h" #include "Materials/MaterialParameterCollection.h" #include "MaterialGraphNode_Knot.h" #include "MaterialEditorActions.h" #include "MaterialExpressionClasses.h" #include "MaterialCompiler.h" #include "EditorSupportDelegates.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Tabs/MaterialEditorTabFactories.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 "IMessageLogListing.h" #include "MessageLogInitializationOptions.h" #include "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 "Materials/MaterialExpressionNamedReroute.h" #include "Materials/MaterialExpressionReroute.h" #include "Materials/MaterialExpressionStrata.h" #include "MaterialStats.h" #include "MaterialEditorTabs.h" #include "MaterialEditorModes.h" #include "Materials/MaterialExpression.h" #include "MaterialCachedHLSLTree.h" #include "SMaterialEditorStrataWidget.h" #include "SMaterialParametersOverviewWidget.h" #include "SMaterialEditorCustomPrimitiveDataWidget.h" #include "IPropertyRowGenerator.h" #include "Widgets/Layout/SScrollBox.h" #include "UObject/TextProperty.h" #include "Subsystems/AssetEditorSubsystem.h" #include "ToolMenus.h" #include "MaterialEditorContext.h" #include "UObject/MetaData.h" #include "ToolMenus.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); /////////////////////////// // FMatExpressionPreview // /////////////////////////// FMatExpressionPreview::FMatExpressionPreview() : FMaterial() , FMaterialRenderProxy(TEXT("FMatExpressionPreview")) , UnrelatedNodesOpacity(1.0f) { // Register this FMaterial derivative with AddEditorLoadedMaterialResource since it does not have a corresponding UMaterialInterface FMaterial::AddEditorLoadedMaterialResource(this); SetQualityLevelProperties(GMaxRHIFeatureLevel); } FMatExpressionPreview::FMatExpressionPreview(UMaterialExpression* InExpression) : FMaterial() , FMaterialRenderProxy(GetPathNameSafe(InExpression->Material)) , UnrelatedNodesOpacity(1.0f) , Expression(InExpression) { FMaterial::AddEditorLoadedMaterialResource(this); FPlatformMisc::CreateGuid(Id); check(InExpression->Material && InExpression->Material->GetExpressions().Contains(InExpression)); SetQualityLevelProperties(GMaxRHIFeatureLevel); UMaterial* BaseMaterial = InExpression->Material; if (BaseMaterial->IsUsingNewHLSLGenerator()) { FMaterialCachedHLSLTree* LocalTree = new FMaterialCachedHLSLTree(); LocalTree->GenerateTree(BaseMaterial, nullptr, InExpression); CachedHLSLTree.Reset(LocalTree); FMaterialCachedExpressionData* LocalCachedData = new FMaterialCachedExpressionData(); LocalCachedData->UpdateForCachedHLSLTree(*LocalTree, nullptr); CachedExpressionData.Reset(LocalCachedData); } else { ReferencedTextures = InExpression->Material->GetReferencedTextures(); } } FMatExpressionPreview::~FMatExpressionPreview() { } void FMatExpressionPreview::AddReferencedObjects(FReferenceCollector& Collector) { Collector.AddReferencedObjects(ReferencedTextures); if (CachedExpressionData) { CachedExpressionData->AddReferencedObjects(Collector); } } 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); if(Strata::IsStrataEnabled()) { // Set the Strata export mode to material preview Compiler->SetStrataMaterialExportType(SME_MaterialPreview, EStrataMaterialExportContext::SMEC_Opaque, 0); } 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; int32 PreviewCodeChunk = INDEX_NONE; PreviewCodeChunk = Expression->CompilePreview(Compiler, OutputIndex); // Get back into gamma corrected space, as DrawTile does not do this adjustment. Ret = Compiler->Power(Compiler->Max(PreviewCodeChunk, Compiler->Constant(0)), Compiler->Constant(1.f / 2.2f)); } else if (Property == MP_WorldPositionOffset || Property == MP_Displacement) { //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 if (Property == MP_FrontMaterial) { // No need to compile the front material: when previewing a node, the FrontMaterial is plugged into the emissive color. // Then CompilePreview is called, and this is where we convert the strata material to a single color for preview. // That single color is then scheduled to be output thanks to setting the compiler as SME_MaterialPreview. return Compiler->StrataCreateAndRegisterNullMaterial(); } else { Ret = Compiler->Constant(1.0f); } // output should always be the right type for this property return Compiler->ForceCast(Ret, FMaterialAttributeDefinitionMap::GetValueType(Property), MFCF_ExactMatch); } UMaterialInterface* FMatExpressionPreview::GetMaterialInterface() const { if (Expression.IsValid()) { UMaterial* ExprMat = Expression->Material; if (ExprMat) { FMaterialRenderProxy* MatProxy = ExprMat->GetRenderProxy(); if (MatProxy) { return MatProxy->GetMaterialInterface(); } } } return nullptr; } void FMatExpressionPreview::NotifyCompilationFinished() { if (Expression.IsValid() && Expression->GraphNode) { CastChecked(Expression->GraphNode)->bPreviewNeedsUpdate = true; } FMaterialRenderProxy::CacheUniformExpressions_GameThread(true); } TArrayView> FMatExpressionPreview::GetReferencedTextures() const { if (CachedExpressionData) { // Path for new HLSL translator return MakeArrayView(CachedExpressionData->ReferencedTextures); } // Legacy path return MakeArrayView(ReferencedTextures); } const FMaterialCachedHLSLTree* FMatExpressionPreview::GetCachedHLSLTree() const { return CachedHLSLTree.Get(); } bool FMatExpressionPreview::IsUsingControlFlow() const { if (Expression.IsValid() && Expression->Material) { return Expression->Material->IsUsingControlFlow(); } return false; } bool FMatExpressionPreview::IsUsingNewHLSLGenerator() const { if (Expression.IsValid() && Expression->Material) { return Expression->Material->IsUsingNewHLSLGenerator(); } return false; } bool FMatExpressionPreview::CheckInValidStateForCompilation(FMaterialCompiler* Compiler) const { return Expression.IsValid() && Expression->Material && Expression->Material->CheckInValidStateForCompilation(Compiler); } ///////////////////// // FMaterialEditor // ///////////////////// void FMaterialEditor::RegisterToolbarTab(const TSharedRef& InTabManager) { FAssetEditorToolkit::RegisterTabSpawners(InTabManager); WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_MaterialEditor", "Material Editor")); auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef(); InTabManager->RegisterTabSpawner(FMaterialEditorTabs::PreviewTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Preview) ) .SetDisplayName( LOCTEXT("ViewportTab", "Viewport") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports")); InTabManager->RegisterTabSpawner(FMaterialEditorTabs::PropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_MaterialProperties) ) .SetDisplayName( LOCTEXT("DetailsTab", "Details") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details")); InTabManager->RegisterTabSpawner(FMaterialEditorTabs::PaletteTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Palette) ) .SetDisplayName( LOCTEXT("PaletteTab", "Palette") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.Tabs.Palette")); InTabManager->RegisterTabSpawner(FMaterialEditorTabs::FindTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Find)) .SetDisplayName(LOCTEXT("FindTab", "Find Results")) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.Tabs.FindResults")); InTabManager->RegisterTabSpawner(FMaterialEditorTabs::PreviewSettingsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_PreviewSettings)) .SetDisplayName( LOCTEXT("PreviewSceneSettingsTab", "Preview Scene Settings") ) .SetGroup( WorkspaceMenuCategoryRef ) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details")); InTabManager->RegisterTabSpawner(FMaterialEditorTabs::ParameterDefaultsTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_ParameterDefaults)) .SetDisplayName(LOCTEXT("ParametersTab", "Parameters")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details")); InTabManager->RegisterTabSpawner(FMaterialEditorTabs::CustomPrimitiveTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_CustomPrimitiveData)) .SetDisplayName(LOCTEXT("CustomPrimitiveTab", "Custom Primitive Data")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details")); InTabManager->RegisterTabSpawner(FMaterialEditorTabs::LayerPropertiesTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_LayerProperties)) .SetDisplayName(LOCTEXT("LayerPropertiesTab", "Layer Parameters")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Layers")); InTabManager->RegisterTabSpawner(FMaterialEditorTabs::StrataTabId, FOnSpawnTab::CreateSP(this, &FMaterialEditor::SpawnTab_Strata)) .SetDisplayName(LOCTEXT("SubstrateTab", "Substrate")) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.Tabs.Palette")); // STRATA_TODO a strata icon MaterialStatsManager->RegisterTabs(); OnRegisterTabSpawners().Broadcast(InTabManager); } void FMaterialEditor::RegisterTabSpawners(const TSharedRef& InTabManager) { DocumentManager->SetTabManager(InTabManager); FWorkflowCentricApplication::RegisterTabSpawners(InTabManager); } void FMaterialEditor::UnregisterTabSpawners(const TSharedRef& InTabManager) { FAssetEditorToolkit::UnregisterTabSpawners(InTabManager); InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::PreviewTabId); InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::PropertiesTabId); InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::PaletteTabId); InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::FindTabId); InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::PreviewSettingsTabId); InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::ParameterDefaultsTabId); InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::CustomPrimitiveTabId); InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::LayerPropertiesTabId); InTabManager->UnregisterTabSpawner(FMaterialEditorTabs::StrataTabId); 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(); // Ensure there are no null entries check(Material->GetExpressions().Find(nullptr) == INDEX_NONE); 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; // Support undo/redo Material->SetFlags(RF_Transactional); GEditor->RegisterForUndo(this); MaterialStatsManager = FMaterialStatsUtils::CreateMaterialStats(this); MaterialStatsManager->SetMaterialDisplayName(OriginalMaterial->GetName()); MaterialStatsManager->GetOldStatsListing()->OnMessageTokenClicked().AddSP(this, &FMaterialEditor::OnMessageLogLinkActivated); 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; bPreviewStaticSwitches = false; 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::NumericParameterDefaultChanged.AddRaw(this, &FMaterialEditor::OnNumericParameterDefaultChanged); 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(); RegisterToolBar(); TSharedPtr ThisPtr(SharedThis(this)); DocumentManager->Initialize(ThisPtr); // Register the document factories { TSharedRef GraphEditorFactory = MakeShareable(new FMaterialGraphEditorSummoner(ThisPtr, FMaterialGraphEditorSummoner::FOnCreateGraphEditorWidget::CreateSP(this, &FMaterialEditor::CreateGraphEditorWidget) )); // Also store off a reference to the grapheditor factory so we can find all the tabs spawned by it later. GraphEditorTabFactoryPtr = GraphEditorFactory; DocumentManager->RegisterDocumentFactory(GraphEditorFactory); } 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 const TSharedRef DummyLayout = FTabManager::NewLayout("NullLayout")->AddArea(FTabManager::NewPrimaryArea()); TArray< UObject* > ObjectsToEdit; ObjectsToEdit.Add(ObjectToEdit); ObjectsToEdit.Add(Material); ObjectsToEdit.Add(MaterialEditorInstance); InitAssetEditor( Mode, InitToolkitHost, MaterialEditorAppIdentifier, DummyLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsToEdit, false ); AddMenuExtender(GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked( "MaterialEditor" ); AddMenuExtender(MaterialEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); ExtendToolbar(); RegenerateMenusAndToolbars(); { AddApplicationMode( FMaterialEditorApplicationModes::StandardMaterialEditorMode, MakeShareable(new FMaterialEditorApplicationMode(SharedThis(this)))); SetCurrentMode(FMaterialEditorApplicationModes::StandardMaterialEditorMode); } // @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); MaterialFunction->MaterialGraph = Material->MaterialGraph; MaterialFunction->EditorMaterial = Material; Material->AssignExpressionCollection(MaterialFunction->GetExpressionCollection()); Material->bEnableExecWire = MaterialFunction->IsUsingControlFlow(); Material->bEnableNewHLSLGenerator = MaterialFunction->IsUsingNewHLSLGenerator(); // Ensure there are no null entries check(Material->GetExpressions().Find(nullptr) == INDEX_NONE); if (Material->GetExpressions().Num() == 0) { // If this is an empty function, create an output by default and start previewing it if (FocusedGraphEdPtr.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->GetExpressions().Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { UMaterialExpression* Expression = Material->GetExpressions()[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); } } //Material->CreateExecutionFlowExpressions(); } // 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 (Generator.IsValid() && MaterialEditorInstance) { UpdateGenerator(); } 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(); } } void FMaterialEditor::UpdateGenerator() { if (MaterialEditorInstance && Generator.IsValid()) { MaterialEditorInstance->RegenerateArrays(); TArray Objects; Objects.Add(MaterialEditorInstance); Generator->SetObjects(Objects); } } void FMaterialEditor::NavigateTab(FDocumentTracker::EOpenDocumentCause InCause) { OpenDocument(nullptr, InCause); } FMaterialEditor::FMaterialEditor() : bMaterialDirty(false) , bStatsFromPreviewMaterial(false) , Material(nullptr) , OriginalMaterial(nullptr) , ExpressionPreviewMaterial(nullptr) , EmptyMaterial(nullptr) , PreviewExpression(nullptr) , MaterialFunction(nullptr) , OriginalMaterialObject(nullptr) , EditorOptions(nullptr) , ScopedTransaction(nullptr) , bAlwaysRefreshAllPreviews(false) , bHideUnusedConnectors(false) , bLivePreview(true) , bIsRealtime(false) , bShowBuiltinStats(false) , bHideUnrelatedNodes(false) , bLockNodeFadeState(false) , bFocusWholeChain(false) , bSelectRegularNode(false) , MenuExtensibilityManager(new FExtensibilityManager) , ToolBarExtensibilityManager(new FExtensibilityManager) , MaterialEditorInstance(nullptr) { DocumentManager = MakeShareable(new FDocumentTracker); } FMaterialEditor::~FMaterialEditor() { // Broadcast that this editor is going down to all listeners OnMaterialEditorClosed().Broadcast(); for (const auto& It : OverriddenNumericParametersToRevert) { SetNumericParameterDefaultOnDependentMaterials(It.Key, It.Value, UE::Shader::FValue(), false); } // Unregister this delegate FEditorSupportDelegates::MaterialUsageFlagsChanged.RemoveAll(this); FEditorSupportDelegates::NumericParameterDefaultChanged.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); FMaterial::DeferredDeleteArray(ExpressionPreviews); } check( !ScopedTransaction ); GEditor->UnregisterForUndo( this ); MaterialEditorInstance = NULL; } void FMaterialEditor::GetAllMaterialExpressionGroups(TArray* OutGroups) { UMaterialEditorOnlyData* EditorOnlyData = Material->GetEditorOnlyData(); if (!EditorOnlyData) { return; } TArray UpdatedGroups; for (const UMaterialExpression* MaterialExpression : Material->GetExpressions()) { FMaterialParameterMetadata ParameterMeta; if (MaterialExpression->GetParameterValue(ParameterMeta)) { const FString GroupName = ParameterMeta.Group.ToString(); OutGroups->AddUnique(GroupName); if (Material->AttemptInsertNewGroupName(GroupName)) { UpdatedGroups.Add(FParameterGroupData(GroupName, 0)); } else { FParameterGroupData* ParameterGroupDataElement = EditorOnlyData->ParameterGroupData.FindByPredicate([&GroupName](const FParameterGroupData& DataElement) { return GroupName == DataElement.GroupName; }); UpdatedGroups.Add(FParameterGroupData(GroupName, ParameterGroupDataElement->GroupSortPriority)); } } } EditorOnlyData->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); } } static void AssignPinSourceIndices(UEdGraphNode* Node) { int32 NumInputDataPins = 0; int32 NumOutputDataPins = 0; int32 NumInputExecPins = 0; int32 NumOutputExecPins = 0; for (UEdGraphPin* Pin : Node->Pins) { int32 SourceIndex = INDEX_NONE; if (Pin->PinType.PinCategory == UMaterialGraphSchema::PC_Exec) { switch (Pin->Direction) { case EGPD_Input: SourceIndex = NumInputExecPins++; break; case EGPD_Output: SourceIndex = NumOutputExecPins++; break; default: checkNoEntry(); break; } } else { switch (Pin->Direction) { case EGPD_Input: SourceIndex = NumInputDataPins++; break; case EGPD_Output: SourceIndex = NumOutputDataPins++; break; default: checkNoEntry(); break; } } if (Pin->SourceIndex == INDEX_NONE) { Pin->SourceIndex = SourceIndex; } ensure(Pin->SourceIndex == SourceIndex); } } void FMaterialEditor::CollapseNodesIntoGraph(UEdGraphNode* InGatewayNode, UMaterialGraphNode* InEntryNode, UMaterialGraphNode* InResultNode, UEdGraph* InSourceGraph, UEdGraph* InDestinationGraph, TSet& InCollapsableNodes) { const UMaterialGraphSchema* MaterialSchema = GetDefault(); // ? // Keep track of the statistics of the node positions so the new nodes can be located reasonably well float SumNodeX = 0.0f; float SumNodeY = 0.0f; float MinNodeX = 1e9f; float MinNodeY = 1e9f; float MaxNodeX = -1e9f; float MaxNodeY = -1e9f; // Move the nodes over, which may create cross-graph references that we need fix up ASAP for (TSet::TConstIterator NodeIt(InCollapsableNodes); NodeIt; ++NodeIt) { UEdGraphNode* Node = *NodeIt; Node->Modify(); // Update stats SumNodeX += Node->NodePosX; SumNodeY += Node->NodePosY; MinNodeX = FMath::Min(MinNodeX, Node->NodePosX); MinNodeY = FMath::Min(MinNodeY, Node->NodePosY); MaxNodeX = FMath::Max(MaxNodeX, Node->NodePosX); MaxNodeY = FMath::Max(MaxNodeY, Node->NodePosY); // Move the node over InSourceGraph->Nodes.Remove(Node); InDestinationGraph->Nodes.Add(Node); Node->Rename(/*NewName=*/ NULL, /*NewOuter=*/ InDestinationGraph); // Move the sub-graph to the new graph if(UMaterialGraphNode_Composite* Composite = Cast(Node)) { InSourceGraph->SubGraphs.Remove(Composite->BoundGraph); InDestinationGraph->SubGraphs.Add(Composite->BoundGraph); Composite->BoundGraph->SubgraphExpression = CastChecked(InGatewayNode)->MaterialExpression; } // Mark the node's expression as owned by the gateway node's expression UMaterialGraphNode* GatewayMaterialNode = CastChecked(InGatewayNode); if (UMaterialGraphNode* MaterialNode = Cast(Node)) { MaterialNode->MaterialExpression->SubgraphExpression = GatewayMaterialNode->MaterialExpression; } else if (UMaterialGraphNode_Comment* CommentNode = Cast(Node)) { CommentNode->MaterialExpressionComment->SubgraphExpression = GatewayMaterialNode->MaterialExpression; } TArray OutputGatewayExecPins; // Find cross-graph links for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex) { UEdGraphPin* LocalPin = Node->Pins[PinIndex]; bool bIsGatewayPin = false; if(LocalPin->LinkedTo.Num()) { for (int32 LinkIndex = 0; LinkIndex < LocalPin->LinkedTo.Num(); ++LinkIndex) { UEdGraphPin* TrialPin = LocalPin->LinkedTo[LinkIndex]; if (!InCollapsableNodes.Contains(TrialPin->GetOwningNode())) { bIsGatewayPin = true; break; } } } // Thunk cross-graph links thru the gateway if (bIsGatewayPin) { // Local port is either the entry or the result node in the collapsed graph // Remote port is the node placed in the source graph UMaterialGraphNode* LocalPort = (LocalPin->Direction == EGPD_Input) ? InEntryNode : InResultNode; // Add a new pin to the entry/exit node and to the composite node UEdGraphPin* LocalPortPin = NULL; UEdGraphPin* RemotePortPin = NULL; if(LocalPin->LinkedTo[0]->GetOwningNode() != InEntryNode) { const FName UniquePortName = InGatewayNode->CreateUniquePinName(LocalPin->PinName); if(!RemotePortPin && !LocalPortPin) { FEdGraphPinType PinType = LocalPin->PinType; RemotePortPin = InGatewayNode->CreatePin(LocalPin->Direction, PinType, UniquePortName); LocalPortPin = LocalPort->CreatePin((LocalPin->Direction == EGPD_Input) ? EGPD_Output : EGPD_Input, PinType, UniquePortName); // Create reroute expressions / pins for the pinbase. UMaterialGraphNode_Composite* CompositeNode = CastChecked(InGatewayNode); UMaterialExpression* GatewayRerouteExpression = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, UMaterialExpressionReroute::StaticClass()); UMaterialExpressionReroute* GatewayReroutePin = CastChecked(GatewayRerouteExpression); UMaterialExpressionPinBase* PinBase = CastChecked(LocalPort->MaterialExpression); GatewayRerouteExpression->SubgraphExpression = CompositeNode->MaterialExpression; PinBase->ReroutePins.Add(FCompositeReroute{UniquePortName, decltype(FCompositeReroute::Expression)(GatewayReroutePin)}); PinBase->Modify(); } } check(LocalPortPin); check(RemotePortPin); LocalPin->Modify(); // Route the links for (int32 LinkIndex = 0; LinkIndex < LocalPin->LinkedTo.Num(); ++LinkIndex) { UEdGraphPin* RemotePin = LocalPin->LinkedTo[LinkIndex]; RemotePin->Modify(); if (!InCollapsableNodes.Contains(RemotePin->GetOwningNode()) && RemotePin->GetOwningNode() != InEntryNode && RemotePin->GetOwningNode() != InResultNode) { // Fix up the remote pin RemotePin->LinkedTo.Remove(LocalPin); RemotePin->MakeLinkTo(RemotePortPin); // The Entry Node only supports a single link, so if we made links above // we need to break them now, to make room for the new link. if (LocalPort == InEntryNode) { LocalPortPin->BreakAllPinLinks(); } // Fix up the local pin LocalPin->LinkedTo.Remove(RemotePin); --LinkIndex; LocalPin->MakeLinkTo(LocalPortPin); } } } } } // Reposition the newly created nodes const int32 NumNodes = InCollapsableNodes.Num(); const float CenterX = NumNodes == 0 ? SumNodeX : SumNodeX / NumNodes; const float CenterY = NumNodes == 0 ? SumNodeY : SumNodeY / NumNodes; const float MinusOffsetX = 160.0f; //@TODO: Random magic numbers const float PlusOffsetX = 300.0f; // Put the gateway node at the center of the empty space in the old graph InGatewayNode->NodePosX = CenterX; InGatewayNode->NodePosY = CenterY; InGatewayNode->SnapToGrid(SNodePanel::GetSnapGridSize()); if (UMaterialGraphNode_Composite* CompositeNode = Cast(InGatewayNode)) { CompositeNode->MaterialExpression->MaterialExpressionEditorX = InGatewayNode->NodePosX; CompositeNode->MaterialExpression->MaterialExpressionEditorY = InGatewayNode->NodePosY; } // Put the entry and exit nodes on either side of the nodes in the new graph if (NumNodes != 0) { InEntryNode->NodePosX = MinNodeX - MinusOffsetX; InEntryNode->NodePosY = CenterY; InEntryNode->SnapToGrid(SNodePanel::GetSnapGridSize()); InEntryNode->MaterialExpression->MaterialExpressionEditorX = InEntryNode->NodePosX; InEntryNode->MaterialExpression->MaterialExpressionEditorY = InEntryNode->NodePosY; InResultNode->NodePosX = MaxNodeX + PlusOffsetX; InResultNode->NodePosY = CenterY; InResultNode->SnapToGrid(SNodePanel::GetSnapGridSize()); InResultNode->MaterialExpression->MaterialExpressionEditorX = InResultNode->NodePosX; InResultNode->MaterialExpression->MaterialExpressionEditorY = InResultNode->NodePosY; } AssignPinSourceIndices(InGatewayNode); AssignPinSourceIndices(InEntryNode); AssignPinSourceIndices(InResultNode); } void FMaterialEditor::CollapseNodes(TSet& InCollapsableNodes) { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (!FocusedGraphEd.IsValid()) { return; } UEdGraph* SourceGraph = FocusedGraphEd->GetCurrentGraph(); SourceGraph->Modify(); // Create the composite node that will serve as the gateway into the subgraph UMaterialGraphNode_Composite* GatewayNode = NULL; { GatewayNode = Cast(FMaterialGraphSchemaAction_NewComposite::SpawnNode(SourceGraph, FVector2D(0, 0))); GatewayNode->bCanRenameNode = true; check(GatewayNode); } UEdGraph* DestinationGraph = GatewayNode->BoundGraph; UMaterialExpressionComposite* CompositeExpression = CastChecked(GatewayNode->MaterialExpression); CollapseNodesIntoGraph(GatewayNode, Cast(CompositeExpression->InputExpressions->GraphNode), Cast(CompositeExpression->OutputExpressions->GraphNode), SourceGraph, DestinationGraph, InCollapsableNodes); AssignPinSourceIndices(GatewayNode); UpdateMaterialAfterGraphChange(); // Now that the expressions are updated, reconstruct the nodes // Need to do this to prevent user from copying a freshly collapsed node not built from its expression GatewayNode->ReconstructNode(); Cast(CompositeExpression->InputExpressions->GraphNode)->ReconstructNode(); Cast(CompositeExpression->OutputExpressions->GraphNode)->ReconstructNode(); } void FMaterialEditor::ExpandNode(UEdGraphNode* InNodeToExpand, UEdGraph* InSourceGraph, TSet& OutExpandedNodes) { UEdGraph* DestinationGraph = InNodeToExpand->GetGraph(); UEdGraph* SourceGraph = InSourceGraph; check(SourceGraph); // Mark all edited objects so they will appear in the transaction record if needed. DestinationGraph->Modify(); SourceGraph->Modify(); InNodeToExpand->Modify(); UEdGraphNode* Entry = nullptr; UEdGraphNode* Result = nullptr; const bool bIsCollapsedGraph = InNodeToExpand->IsA(); MoveNodesToGraph(MutableView(SourceGraph->Nodes), DestinationGraph, OutExpandedNodes, &Entry, &Result, bIsCollapsedGraph); CollapseGatewayNode(InNodeToExpand, Entry, Result, &OutExpandedNodes); bool bPreviewExpressionDeleted = false; if (Entry) { UMaterialExpressionPinBase* PinBase = Cast(Cast(Entry)->MaterialExpression); PinBase->DeleteReroutePins(); Material->GetExpressionCollection().RemoveExpression(PinBase); PinBase->MarkAsGarbage(); Entry->DestroyNode(); bPreviewExpressionDeleted |= PinBase == PreviewExpression; } if (Result) { UMaterialExpressionPinBase* PinBase = Cast(Cast(Result)->MaterialExpression); PinBase->DeleteReroutePins(); Material->GetExpressionCollection().RemoveExpression(PinBase); PinBase->MarkAsGarbage(); Result->DestroyNode(); bPreviewExpressionDeleted |= PinBase == PreviewExpression; } // Make sure any subgraphs get propagated appropriately if (SourceGraph->SubGraphs.Num() > 0) { DestinationGraph->SubGraphs.Append(SourceGraph->SubGraphs); SourceGraph->SubGraphs.Empty(); } // Remove the gateway node and source graph UMaterialExpression* CompositeExpression = Cast(InNodeToExpand)->MaterialExpression; CompositeExpression->Modify(); Material->GetExpressionCollection().RemoveExpression(CompositeExpression); CompositeExpression->MarkAsGarbage(); InNodeToExpand->DestroyNode(); bPreviewExpressionDeleted |= CompositeExpression == PreviewExpression; if (bPreviewExpressionDeleted) { // The preview expression was deleted. Null out our reference to it and reset to the normal preview material SetPreviewExpression(nullptr); RegenerateCodeView(); UpdatePreviewMaterial(); } } void FMaterialEditor::MoveNodesToAveragePos(TSet& AverageNodes, FVector2D SourcePos, bool bExpandedNodesNeedUniqueGuid) const { if (AverageNodes.Num() > 0) { FVector2D AvgNodePosition(0.0f, 0.0f); for (TSet::TIterator It(AverageNodes); It; ++It) { UEdGraphNode* Node = *It; AvgNodePosition.X += Node->NodePosX; AvgNodePosition.Y += Node->NodePosY; } float InvNumNodes = 1.0f / float(AverageNodes.Num()); AvgNodePosition.X *= InvNumNodes; AvgNodePosition.Y *= InvNumNodes; TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); for (UEdGraphNode* ExpandedNode : AverageNodes) { ExpandedNode->NodePosX = (ExpandedNode->NodePosX - AvgNodePosition.X) + SourcePos.X; ExpandedNode->NodePosY = (ExpandedNode->NodePosY - AvgNodePosition.Y) + SourcePos.Y; ExpandedNode->SnapToGrid(SNodePanel::GetSnapGridSize()); if (bExpandedNodesNeedUniqueGuid) { ExpandedNode->CreateNewGuid(); } //Add expanded node to selection FocusedGraphEd->SetNodeSelection(ExpandedNode, true); } } } void FMaterialEditor::MoveNodesToGraph(TArray& SourceNodes, UEdGraph* DestinationGraph, TSet& OutExpandedNodes, UEdGraphNode** OutEntry, UEdGraphNode** OutResult, const bool bIsCollapsedGraph) { // Move the nodes over, remembering any that are boundary nodes while (SourceNodes.Num()) { UEdGraphNode* Node = SourceNodes.Pop(); UEdGraph* OriginalGraph = Node->GetGraph(); Node->Modify(); OriginalGraph->Modify(); Node->Rename(/*NewName=*/ nullptr, /*NewOuter=*/ DestinationGraph, REN_DontCreateRedirectors); // Remove the node from the original graph OriginalGraph->RemoveNode(Node, false); // We do not check CanPasteHere when determining CanCollapseNodes, unlike CanCollapseSelectionToFunction/Macro, // so when expanding a collapsed graph we don't want to check the CanPasteHere function: if (!bIsCollapsedGraph && !Node->CanPasteHere(DestinationGraph)) { Node->BreakAllNodeLinks(); continue; } // Successfully added the node to the graph, we may need to remove flags if (Node->HasAllFlags(RF_Transient) && !DestinationGraph->HasAllFlags(RF_Transient)) { Node->SetFlags(RF_Transactional); Node->ClearFlags(RF_Transient); TArray Subobjects; GetObjectsWithOuter(Node, Subobjects); for (UObject* Subobject : Subobjects) { Subobject->ClearFlags(RF_Transient); Subobject->SetFlags(RF_Transactional); } } DestinationGraph->AddNode(Node, /* bFromUI */ false, /* bSelectNewNode */ false); if (UMaterialGraphNode_Composite* Composite = Cast(Node)) { OriginalGraph->SubGraphs.Remove(Composite->BoundGraph); DestinationGraph->SubGraphs.Add(Composite->BoundGraph); } UMaterialGraphNode* MaterialNode = Cast(Node); if (MaterialNode && MaterialNode->MaterialExpression->IsA(UMaterialExpressionPinBase::StaticClass())) { UMaterialExpressionPinBase* PinBase = Cast(MaterialNode->MaterialExpression); if (PinBase && PinBase->PinDirection == EGPD_Output) { *OutEntry = Node; } else if (PinBase && PinBase->PinDirection == EGPD_Input) { *OutResult = Node; } } else { OutExpandedNodes.Add(Node); } } } bool FMaterialEditor::CollapseGatewayNode(UEdGraphNode* InNode, UEdGraphNode* InEntryNode, UEdGraphNode* InResultNode, TSet* OutExpandedNodes) { bool bSuccessful = true; // We iterate the array in reverse so we can both remove the subpins safely after we've read them and // so we have split nested structs we combine them back together in the right order for (int32 BoundaryPinIndex = InNode->Pins.Num() - 1; BoundaryPinIndex >= 0; --BoundaryPinIndex) { UEdGraphPin* const BoundaryPin = InNode->Pins[BoundaryPinIndex]; // For each pin in the gateway node, find the associated pin in the entry or result node. UEdGraphNode* const GatewayNode = (BoundaryPin->Direction == EGPD_Input) ? InEntryNode : InResultNode; UEdGraphPin* GatewayPin = nullptr; if (GatewayNode) { for (int32 PinIdx = GatewayNode->Pins.Num() - 1; PinIdx >= 0; --PinIdx) { UEdGraphPin* const Pin = GatewayNode->Pins[PinIdx]; // Function graphs have a single exec path through them, so only one exec pin for input and another for output. In this fashion, they must not be handled by name. if ((Pin->PinName == BoundaryPin->PinName) && (Pin->Direction != BoundaryPin->Direction)) { GatewayPin = Pin; break; } } } if (GatewayPin) { //@TODO: This is same as UEdGraphSchema_K2::CombineTwoPinNetsAndRemoveOldPins, except we don't care about default values // since material graphs pins can't do that. More consolidation auto CombineTwoPinNetsAndRemoveOldPins = [](UEdGraphPin* InPinA, UEdGraphPin* InPinB) { // Make direct connections between the things that connect to A or B, removing A and B from the picture for (int32 IndexA = 0; IndexA < InPinA->LinkedTo.Num(); ++IndexA) { UEdGraphPin* FarA = InPinA->LinkedTo[IndexA]; // TODO: Michael N. says this if check should be unnecessary once the underlying issue is fixed. // (Probably should use a check() instead once it's removed though. See additional cases above. if (FarA != nullptr) { for (int32 IndexB = 0; IndexB < InPinB->LinkedTo.Num(); ++IndexB) { UEdGraphPin* FarB = InPinB->LinkedTo[IndexB]; if (FarB != nullptr) { FarA->Modify(); FarB->Modify(); FarA->MakeLinkTo(FarB); } } } } }; CombineTwoPinNetsAndRemoveOldPins(BoundaryPin, GatewayPin); } } return bSuccessful; } 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) const { return NodeFeatureLevel == TestFeatureLevel; } bool FMaterialEditor::IsFeaturePreviewAvailable(ERHIFeatureLevel::Type TestFeatureLevel) const { return GMaxRHIFeatureLevel >= TestFeatureLevel; } BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION void FMaterialEditor::CreateInternalWidgets() { PreviewViewport = SNew(SMaterialEditor3DPreviewViewport) .MaterialEditor(SharedThis(this)); PreviewUIViewport = SNew(SMaterialEditorUIPreviewViewport, Material); FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked( "PropertyEditor" ); FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea; DetailsViewArgs.bHideSelectionTip = true; DetailsViewArgs.NotifyHook = 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 ); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionRuntimeVirtualTextureSampleParameter::StaticClass(), LayoutExpressionParameterDetails ); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionSparseVolumeTextureSampleParameter::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 ); FOnGetDetailCustomizationInstance LayoutCompositeDetails = FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialExpressionCompositeDetails::MakeInstance); MaterialDetailsView->RegisterInstancedCustomPropertyLayout( UMaterialExpressionComposite::StaticClass(), LayoutCompositeDetails ); MaterialDetailsView->OnFinishedChangingProperties().AddSP(this, &FMaterialEditor::OnFinishedChangingProperties); PropertyEditorModule.RegisterCustomClassLayout( UMaterial::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialDetailCustomization::MakeInstance ) ); PropertyEditorModule.RegisterCustomClassLayout( UMaterialFunction::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FMaterialFunctionDetailCustomization::MakeInstance) ); MaterialEditorInstance = NewObject(GetTransientPackage(), NAME_None, RF_Transactional); MaterialEditorInstance->PreviewMaterial = Material; MaterialEditorInstance->OriginalMaterial = OriginalMaterial; if (MaterialFunction) { MaterialEditorInstance->OriginalFunction = MaterialFunction->ParentFunction; } FPropertyEditorModule& Module = FModuleManager::LoadModuleChecked("PropertyEditor"); if (!Generator.IsValid()) { FPropertyRowGeneratorArgs Args; Generator = Module.CreatePropertyRowGenerator(Args); } MaterialParametersOverviewWidget = SNew(SMaterialParametersOverviewPanel) .InMaterialEditorInstance(MaterialEditorInstance) .InGenerator(Generator); Generator->OnFinishedChangingProperties().AddSP(this, &FMaterialEditor::OnFinishedChangingParametersFromOverview); Generator->OnRowsRefreshed().AddSP(this, &FMaterialEditor::GeneratorRowsRefreshed); MaterialCustomPrimitiveDataWidget = SNew(SMaterialCustomPrimitiveDataPanel, MaterialEditorInstance); MaterialLayersFunctionsInstance = SNew(SMaterialLayersFunctionsMaterialWrapper) .InMaterialEditorInstance(MaterialEditorInstance) .InGenerator(Generator); 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)); StrataWidget = SNew(SMaterialEditorStrataWidget, SharedThis(this)); RegenerateCodeView(); } END_SLATE_FUNCTION_BUILD_OPTIMIZATION void FMaterialEditor::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) { // If this is a comment only change, skip updating the preview material and viewport { bool bIsCommentOnlyChange = true; for (int32 Index = 0; Index < PropertyChangedEvent.GetNumObjectsBeingEdited(); ++Index) { const UObject* EditedObject = PropertyChangedEvent.GetObjectBeingEdited(Index); if (!EditedObject->IsA()) { bIsCommentOnlyChange = false; break; } if (EditedObject->IsA()) { continue; } if (PropertyChangedEvent.GetPropertyName() != TEXT("Desc")) { bIsCommentOnlyChange = false; break; } } if (bIsCommentOnlyChange) { return; } } bool bRefreshNodePreviews = false; if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { FStructProperty* Property = CastField(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(); MaterialCustomPrimitiveDataWidget->UpdateEditorInstance(MaterialEditorInstance); } } void FMaterialEditor::OnFinishedChangingParametersFromOverview(const FPropertyChangedEvent& PropertyChangedEvent) { bool bRefreshNodePreviews = false; if (PropertyChangedEvent.Property != nullptr && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive) { RefreshExpressionPreviews(true); RefreshPreviewViewport(); TArray> SelectedObjects = MaterialDetailsView->GetSelectedObjects(); // Don't set directly on color structs since the color picker will do that for us FStructProperty* StructProperty = CastField(PropertyChangedEvent.Property); if (!StructProperty || StructProperty->Struct->GetFName() != "LinearColor") { MaterialDetailsView->SetObjects(SelectedObjects, true); } Material->MarkPackageDirty(); SetMaterialDirty(); } } void FMaterialEditor::OnChangeBreadCrumbGraph(UEdGraph* InGraph) { if (InGraph && FocusedGraphEdPtr.IsValid()) { OpenDocument(InGraph, FDocumentTracker::NavigatingCurrentDocument); } } void FMaterialEditor::GeneratorRowsRefreshed() { MaterialParametersOverviewWidget->Refresh(); if (MaterialLayersFunctionsInstance) { MaterialLayersFunctionsInstance->Refresh(); } } 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() { AddToolbarExtender(GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); IMaterialEditorModule* MaterialEditorModule = &FModuleManager::LoadModuleChecked( "MaterialEditor" ); AddToolbarExtender(MaterialEditorModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); } void FMaterialEditor::InitToolMenuContext(FToolMenuContext& MenuContext) { FAssetEditorToolkit::InitToolMenuContext(MenuContext); UMaterialEditorMenuContext* Context = NewObject(); Context->MaterialEditor = SharedThis(this); MenuContext.AddObject(Context); } void FMaterialEditor::RegisterToolBar() { const FName MenuName = FAssetEditorToolkit::GetToolMenuToolbarName(); if (!UToolMenus::Get()->IsMenuRegistered(MenuName)) { UToolMenu* ToolBar = UToolMenus::Get()->RegisterMenu(MenuName, "AssetEditor.DefaultToolBar", EMultiBoxType::ToolBar); FToolMenuInsert InsertAfterAssetSection("Asset", EToolMenuInsertType::After); FToolMenuSection& MaterialSection = ToolBar->AddSection("MaterialTools", TAttribute(), InsertAfterAssetSection); MaterialSection.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().Apply)); MaterialSection.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().FindInMaterial)); MaterialSection.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().CameraHome)); UMaterialEditorMenuContext* Context = ToolBar->FindContext(); if (!MaterialFunction) { MaterialSection.AddEntry(FToolMenuEntry::InitComboButton( "Hierarchy", FToolUIActionChoice(), FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu) { UMaterialEditorMenuContext* SubMenuContext = InSubMenu->FindContext(); if (SubMenuContext && SubMenuContext->MaterialEditor.IsValid()) { SubMenuContext->MaterialEditor.Pin()->GenerateInheritanceMenu(InSubMenu); } }), LOCTEXT("Hierarchy", "Hierarchy"), FText::GetEmpty(), FSlateIcon(FAppStyle::Get().GetStyleSetName(), "MaterialEditor.Hierarchy"), false )); } FToolMenuEntry LiveUpdateMenu = FToolMenuEntry::InitComboButton( "LiveUpdate", FUIAction(), FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu) { FToolMenuSection& Section = InSubMenu->FindOrAddSection("LiveUpdateOptions"); Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().ToggleLivePreview)); Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().ToggleRealtimeExpressions)); Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().AlwaysRefreshAllPreviews)); }), LOCTEXT("LiveUpdate_Label", "Live Update"), LOCTEXT("LiveUpdate_Tooltip", "Set the elements of the Material Editor UI to update in realtime"), FSlateIcon(FAppStyle::Get().GetStyleSetName(), "MaterialEditor.LiveUpdate") ); LiveUpdateMenu.StyleNameOverride = "CalloutToolbar"; MaterialSection.AddEntry(LiveUpdateMenu); FToolMenuSection& GraphSection = ToolBar->AddSection("Graph", TAttribute(), InsertAfterAssetSection); FToolMenuEntry CleaningMenu = FToolMenuEntry::InitComboButton( "CleanGraph", FUIAction(), FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu) { FToolMenuSection& Section = InSubMenu->FindOrAddSection("General"); Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().CleanUnusedExpressions)); Section.AddEntry(FToolMenuEntry::InitMenuEntry(FMaterialEditorCommands::Get().ShowHideConnectors)); }), LOCTEXT("GraphCleanup_Label", "Clean Graph"), LOCTEXT("GraphCleanup_Tooltip", "Tools to help clean up graph nodes and connections"), FSlateIcon(FAppStyle::Get().GetStyleSetName(), "GraphEditor.Clean") ); CleaningMenu.StyleNameOverride = "CalloutToolbar"; GraphSection.AddEntry(CleaningMenu); GraphSection.AddEntry(FToolMenuEntry::InitComboButton( "NodePreview", FUIAction(), FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu) { UMaterialEditorMenuContext* SubMenuContext = InSubMenu->FindContext(); if (SubMenuContext && SubMenuContext->MaterialEditor.IsValid()) { TSharedPtr MaterialEditor = StaticCastSharedPtr(SubMenuContext->MaterialEditor.Pin()); MaterialEditor->GeneratePreviewMenuContent(InSubMenu); } }), LOCTEXT("NodePreview_Label", "Preview State"), LOCTEXT("NodePreviewToolTip", "Preview the graph state for a given feature level, material quality, or static switch value."), FSlateIcon(FAppStyle::GetAppStyleSetName(), "FullBlueprintEditor.SwitchToScriptingMode"), false )); GraphSection.AddEntry(FToolMenuEntry::InitToolBarButton( FMaterialEditorCommands::Get().ToggleHideUnrelatedNodes, TAttribute(), TAttribute(), FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.ToggleHideUnrelatedNodes") )); GraphSection.AddEntry(FToolMenuEntry::InitComboButton( "HideUnrelatedNodesOptions", FUIAction(), FNewToolMenuDelegate::CreateLambda([](UToolMenu* InSubMenu) { UMaterialEditorMenuContext* SubMenuContext = InSubMenu->FindContext(); if (SubMenuContext && SubMenuContext->MaterialEditor.IsValid()) { TSharedPtr MaterialEditor = StaticCastSharedPtr(SubMenuContext->MaterialEditor.Pin()); MaterialEditor->MakeHideUnrelatedNodesOptionsMenu(InSubMenu); } }), LOCTEXT("HideUnrelatedNodesOptions", "Hide Unrelated Nodes Options"), LOCTEXT("HideUnrelatedNodesOptionsMenu", "Hide Unrelated Nodes options menu"), TAttribute(), true )); { FToolMenuSection& Section = ToolBar->AddSection("Stats", TAttribute(), InsertAfterAssetSection); Section.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().ToggleMaterialStats)); Section.AddEntry(FToolMenuEntry::InitToolBarButton(FMaterialEditorCommands::Get().TogglePlatformStats)); } } }; void FMaterialEditor::AddInheritanceMenuEntry(FToolMenuSection& Section, const FAssetData& AssetData, bool bIsFunctionPreviewMaterial) { FExecuteAction OpenAction; if (bIsFunctionPreviewMaterial) { OpenAction.BindStatic(&FMaterialEditorUtilities::OnOpenFunction, AssetData); } else { OpenAction.BindStatic(&FMaterialEditorUtilities::OnOpenMaterial, AssetData); } FFormatNamedArguments Args; Args.Add(TEXT("ParentName"), FText::FromName(AssetData.AssetName)); FText Label = FText::Format(LOCTEXT("InstanceParentName", "{ParentName}"), Args); Section.AddEntry(FToolMenuEntry::InitMenuEntry( NAME_None, Label, LOCTEXT("OpenInEditor", "Open In Editor"), FSlateIcon(), FUIAction(OpenAction) )); } void FMaterialEditor::GenerateInheritanceMenu(UToolMenu* Menu) { RebuildInheritanceList(); Menu->bShouldCloseWindowAfterMenuSelection = true; Menu->SetMaxHeight(500); if (!MaterialFunction) { FToolMenuSection& Section = Menu->AddSection("MaterialInstances", LOCTEXT("MaterialInstances", "Material Instances")); if (MaterialChildList.Num() == 0) { const FText NoChildText = LOCTEXT("NoInstancesFound", "No Instances Found"); TSharedRef NoChildWidget = SNew(STextBlock) .Text(NoChildText); Section.AddEntry(FToolMenuEntry::InitWidget("NoInstancesFound", NoChildWidget, FText::GetEmpty())); } for (const FAssetData& MaterialChild : MaterialChildList) { FMaterialEditor::AddInheritanceMenuEntry(Section, MaterialChild, false); } } } void FMaterialEditor::GeneratePreviewMenuContent(UToolMenu* Menu) { Menu->bShouldCloseWindowAfterMenuSelection = true; FToolMenuSection& QualityLevelSection = Menu->AddSection("MaterialEditorQualityPreview", LOCTEXT("MaterialQualityHeading", "Quality Level")); { QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_All); QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Epic); QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_High); QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Medium); QualityLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().QualityLevel_Low); } FToolMenuSection& FeatureLevelSection = Menu->AddSection("MaterialEditorFeaturePreview", LOCTEXT("MaterialFeatureHeading", "Feature Level")); { FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_All); FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_Mobile); FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM5); FeatureLevelSection.AddMenuEntry(FMaterialEditorCommands::Get().FeatureLevel_SM6); } FToolMenuSection& StaticSwitchSection = Menu->AddSection("StaticSwitchPreview", LOCTEXT("StaticSwitchHeading", "Switch Params")); TSharedRef StaticSwitchCheckbox = SNew(SBox) [ SNew(SCheckBox) .IsChecked(bPreviewStaticSwitches ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) .OnCheckStateChanged_Lambda( [this](ECheckBoxState NewState) { bPreviewStaticSwitches = (NewState == ECheckBoxState::Checked); bPreviewFeaturesChanged = true; } ) .Style(FAppStyle::Get(), "Menu.CheckBox") .ToolTipText(LOCTEXT("StaticSwitchCheckBoxToolTip", "Hide disabled nodes in the graph, according to switch params.")) .Content() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(2.0f, 0.0f, 0.0f, 0.0f) [ SNew(STextBlock) .Text(LOCTEXT("DisableInactiveSwitch", "Hide Disabled")) ] ] ]; StaticSwitchSection.AddEntry(FToolMenuEntry::InitMenuEntry("StaticSwitchCheckbox", FUIAction(), StaticSwitchCheckbox)); } 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(EAssetEditorCloseReason InCloseReason) { DestroyColorPicker(); // If the asset has been deleted, we don't want to show the save changes prompt if(InCloseReason == EAssetEditorCloseReason::AssetForceDeleted) { bMaterialDirty = false; } 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 the changes of the modified material to the original material?\n{0}\n(Selecting 'No' will cause all changes to be lost!)"), 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::AddGraphEditorPinActionsToContextMenu(FToolMenuSection& InSection) const { // Promote To Parameter { FToolUIAction PromoteToParameterAction; PromoteToParameterAction.ExecuteAction = FToolMenuExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteToParameter); PromoteToParameterAction.IsActionVisibleDelegate = FToolMenuIsActionButtonVisible::CreateSP(this, &FMaterialEditor::OnCanPromoteToParameter); TSharedPtr PromoteToParameterCommand = FMaterialEditorCommands::Get().PromoteToParameter; InSection.AddMenuEntry( PromoteToParameterCommand->GetCommandName(), PromoteToParameterCommand->GetLabel(), PromoteToParameterCommand->GetDescription(), PromoteToParameterCommand->GetIcon(), PromoteToParameterAction ); } { auto AddStrataContextualMenu = [&](TSharedPtr CreateNodeCommand, EStrataNodeForPin StrataNodeForPin) { FToolUIAction CreateNodeAction; CreateNodeAction.ExecuteAction = FToolMenuExecuteAction::CreateSP(this, &FMaterialEditor::OnCreateStrataNodeForPin, StrataNodeForPin); CreateNodeAction.IsActionVisibleDelegate = FToolMenuIsActionButtonVisible::CreateSP(this, &FMaterialEditor::OnCanCreateStrataNodeForPin, StrataNodeForPin); InSection.AddMenuEntry( CreateNodeCommand->GetCommandName(), CreateNodeCommand->GetLabel(), CreateNodeCommand->GetDescription(), CreateNodeCommand->GetIcon(), CreateNodeAction ); }; AddStrataContextualMenu(FMaterialEditorCommands::Get().CreateSlabNode, EStrataNodeForPin::Slab); AddStrataContextualMenu(FMaterialEditorCommands::Get().CreateHorizontalMixNode, EStrataNodeForPin::HorizontalMix); AddStrataContextualMenu(FMaterialEditorCommands::Get().CreateVerticalLayerNode, EStrataNodeForPin::VerticalLayer); AddStrataContextualMenu(FMaterialEditorCommands::Get().CreateWeightNode, EStrataNodeForPin::Weight); } } bool FMaterialEditor::MatchesContext(const FTransactionContext& InContext, const TArray>& TransactionObjectContexts) const { for (const TPair& TransactionObjectContext : TransactionObjectContexts) { UObject* Object = TransactionObjectContext.Get<0>(); // Evaluate whether any object we are interested in matches an object part of the transaction. bool bIsMaterialRelatedObject = Object->IsA() || Object->IsA() || Object->IsA() || Object->IsA() || Object->IsA() || Object->IsA() || Object->IsA() || Object->IsA() || Object->IsA() || Object->IsA() || Object->IsA() || Object->IsA(); if (bIsMaterialRelatedObject) { return true; } } return false; } 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; } uint32 NumVirtualTextureLookups = MaterialResource->GetEstimatedNumVirtualTextureLookups(); if (NumVirtualTextureLookups > 0) { Canvas->DrawShadowedString( 5, DrawPositionY, *FString::Printf(TEXT("Virtual Texture Lookups (Est.): %u"), NumVirtualTextureLookups), FontToUse, FLinearColor(1, 1, 0) ); DrawPositionY += SpacingBetweenLines; } const uint32 NumVirtualTextureStacks = MaterialResource->GetNumVirtualTextureStacks(); if (NumVirtualTextureStacks > 0) { Canvas->DrawShadowedString( 5, DrawPositionY, *FString::Printf(TEXT("Virtual Texture Stacks: %u"), NumVirtualTextureStacks), FontToUse, FLinearColor(1, 1, 0) ); DrawPositionY += SpacingBetweenLines; } TStaticArray LWCFuncUsages = MaterialResource->GetEstimatedLWCFuncUsages(); for (int KindIndex = 0; KindIndex < (int)ELWCFunctionKind::Max; ++KindIndex) { int Usages = LWCFuncUsages[KindIndex]; if (LWCFuncUsages[KindIndex] > 0) { Canvas->DrawShadowedString( 5, DrawPositionY, *FString::Printf(TEXT("LWC %s usages (Est.): %u"), *UEnum::GetDisplayValueAsText((ELWCFunctionKind)KindIndex).ToString(), Usages), 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() { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (!FocusedGraphEd) { return; } UEdGraphNode* FocusNode = NULL; if (MaterialFunction) { bool bSetPreviewExpression = false; UMaterialExpressionFunctionOutput* FirstOutput = NULL; for (int32 ExpressionIndex = Material->GetExpressions().Num() - 1; ExpressionIndex >= 0; ExpressionIndex--) { UMaterialExpression* Expression = Material->GetExpressions()[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; FocusedGraphEd->GetViewLocation(CurrLocation, CurrZoomLevel); FocusedGraphEd->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); } if (PreviewViewport.IsValid()) { PreviewViewport->SetPreviewMaterial(nullptr); } } else { if (PreviewUIViewport.IsValid()) { PreviewUIViewport->SetPreviewMaterial(nullptr); } if (PreviewViewport.IsValid()) { PreviewViewport->SetPreviewMaterial(InMaterialInterface); } } } void FMaterialEditor::RefreshPreviewViewport() { if (PreviewViewport.IsValid()) { PreviewViewport->RefreshViewport(); } } void FMaterialEditor::LoadEditorSettings() { EditorOptions = NewObject(); if (EditorOptions->bHideUnusedConnectorsSetting) {OnHideConnectors();} 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->GetViewportClient()) { PreviewViewport->GetViewportClient()->SetRealtime(true); } } if (EditorOptions->bHideUnrelatedNodes) { ToggleHideUnrelatedNodes(); } // 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->bHideUnusedConnectorsSetting = IsOnHideConnectorsChecked(); EditorOptions->bAlwaysRefreshAllPreviews = IsOnAlwaysRefreshAllPreviews(); EditorOptions->bRealtimeExpressionViewport = IsToggleRealTimeExpressionsChecked(); EditorOptions->bLivePreviewUpdate = IsToggleLivePreviewChecked(); EditorOptions->bHideUnrelatedNodes = bHideUnrelatedNodes; 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->AssignExpressionCollection(Material->GetExpressionCollection()); ExpressionPreviewMaterial->bEnableExecWire = Material->IsUsingControlFlow(); ExpressionPreviewMaterial->bEnableNewHLSLGenerator = Material->IsUsingNewHLSLGenerator(); if (MaterialFunction) { ExpressionPreviewMaterial->BlendMode = MaterialFunction->PreviewBlendMode; } 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(); } Material->MaterialGraph->UpdatePinTypes(); if (!PreviewExpression) { UpdateStatsMaterials(); // Null out the expression preview material so they can be GC'ed ExpressionPreviewMaterial = NULL; } 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::Num - 1; i >= 0; --i) { ERHIFeatureLevel::Type FeatureLevel = (ERHIFeatureLevel::Type)i; FMaterialResource* CurrentResource = Material->GetMaterialResource(FeatureLevel); if(CurrentResource && CurrentResource->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->AssignExpressionCollection(Material->GetExpressionCollection()); // Preserve the thumbnail info UThumbnailInfo* OriginalThumbnailInfo = MaterialFunction->ParentFunction->ThumbnailInfo; UThumbnailInfo* ThumbnailInfo = MaterialFunction->ThumbnailInfo; MaterialFunction->ParentFunction->ThumbnailInfo = NULL; MaterialFunction->ThumbnailInfo = NULL; // Cache any metadata const TMap* MetaData = UMetaData::GetMapForObject(MaterialFunction->ParentFunction); // 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 the metadata if (MetaData) { UMetaData* PackageMetaData = MaterialFunction->ParentFunction->GetOutermost()->GetMetaData(); PackageMetaData->SetObjectValues(MaterialFunction->ParentFunction, *MetaData); } // 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 (UMaterialExpression* CurrentExpression : MaterialFunction->ParentFunction->GetExpressions()) { ensureMsgf(CurrentExpression, TEXT("Invalid expression whilst saving material function.")); // Link the expressions back to their function if (CurrentExpression) { CurrentExpression->Material = NULL; CurrentExpression->Function = MaterialFunction->ParentFunction; } } for (UMaterialExpressionComment* CurrentExpression : MaterialFunction->ParentFunction->GetEditorComments()) { ensureMsgf(CurrentExpression, TEXT("Invalid comment whilst saving material function.")); // 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; // Cache any metadata const TMap* MetaData = UMetaData::GetMapForObject(OriginalMaterial); // A bit hacky, but disable material compilation in post load when we duplicate the material. UMaterial::ForceNoCompilationInPostLoad(true); // Now, the material has been loaded and serialized. PostLoad has been called and the asset has been converted and now it is up to date. // Let's thus reset the linker (to the last version) to make sure PostLoad on the duplicated object is not doing any data conversion, squashing new properties with not properly initialized _DEPRECATED members (e.g. RefractionMode_DEPRECATED). ResetLoaders(OriginalMaterial->GetPackage()); // 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; // Restore the metadata if (MetaData) { UMetaData* PackageMetaData = OriginalMaterial->GetOutermost()->GetMetaData(); PackageMetaData->SetObjectValues(OriginalMaterial, *MetaData); } // 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::OnMessageLogLinkActivated(const class TSharedRef& Token) { const TSharedRef UObjectToken = StaticCastSharedRef(Token); if (UObjectToken->GetObject().IsValid()) { UMaterialExpression* Expression = Cast(UObjectToken->GetObject().Get()); if(UObject* MaterialOrFunction = Expression->GetAssetOwner()) { UAssetEditorSubsystem* AssetEditor = GEditor->GetEditorSubsystem(); if(AssetEditor->OpenEditorForAsset(MaterialOrFunction)) { FMaterialEditor* TargetEditor = static_cast(AssetEditor->FindEditorForAsset(MaterialOrFunction, true)); checkf(TargetEditor, TEXT("Could not find Editor for Asset: %s"), *(MaterialOrFunction->GetFName().ToString())); FMaterialExpressionCollection& Collection = Expression->Function ? TargetEditor->MaterialFunction->GetEditorOnlyData()->ExpressionCollection : TargetEditor->Material->GetEditorOnlyData()->ExpressionCollection; for (const TObjectPtr& EditorExpression : Collection.Expressions) { if (EditorExpression->MaterialExpressionGuid == Expression->MaterialExpressionGuid && EditorExpression->MaterialExpressionEditorX == Expression->MaterialExpressionEditorX && EditorExpression->MaterialExpressionEditorY == Expression->MaterialExpressionEditorY && EditorExpression->GetName() == Expression->GetName()) { if (EditorExpression->GraphNode) { TargetEditor->JumpToNode(EditorExpression->GraphNode); break; } } } } } } } 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; TArray FailingExpression; ERHIFeatureLevel::Type FeatureLevel = FeatureLevelsToDisplay[i]; const FMaterialResource* MaterialResource = MaterialForStats->GetMaterialResource(FeatureLevel); if (MaterialResource == nullptr) { continue; } 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->GetExpressions()) { if (UMaterialExpressionFunctionInput* InputExpression = Cast(Expression)) { ++NumInputs; if (NumInputs > 1 || !InputExpression->IsResultMaterialAttributes(0)) { CompileErrors.Add(TEXT("Layer graphs only support a single material attributes input.")); FailingExpression.Add(nullptr); } } 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.")); FailingExpression.Add(nullptr); } } else if (UMaterialExpressionMaterialAttributeLayers* RecursiveLayer = Cast(Expression)) { CompileErrors.Add(TEXT("Layer graphs do not support layers within layers.")); FailingExpression.Add(nullptr); } } if (NumInputs > 1 || NumOutputs < 1) { CompileErrors.Add(TEXT("Layer graphs require a single material attributes output and optionally, a single material attributes input.")); FailingExpression.Add(nullptr); } } else if (MaterialFunction->GetMaterialFunctionUsage() == EMaterialFunctionUsage::MaterialLayerBlend) { // Material layer blends can have two MA inputs and single MA output only for (UMaterialExpression* Expression : MaterialFunction->GetExpressions()) { if (UMaterialExpressionFunctionInput* InputExpression = Cast(Expression)) { ++NumInputs; if (NumInputs > 2 || !InputExpression->IsResultMaterialAttributes(0)) { CompileErrors.Add(TEXT("Layer blend graphs only support two material attributes inputs.")); FailingExpression.Add(nullptr); } } 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.")); FailingExpression.Add(nullptr); } } else if (UMaterialExpressionMaterialAttributeLayers* RecursiveLayer = Cast(Expression)) { CompileErrors.Add(TEXT("Layer blend graphs do not support layers within layers.")); FailingExpression.Add(nullptr); } } if (NumOutputs < 1) { CompileErrors.Add(TEXT("Layer blend graphs can have up to two material attributes inputs and a single output.")); FailingExpression.Add(nullptr); } } else { // Add a compile error message for functions missing an output FMaterialResource* CurrentResource = ExpressionPreviewMaterial->GetMaterialResource(FeatureLevel); if (CurrentResource) { CompileErrors = CurrentResource->GetCompileErrors(); FailingExpression = CurrentResource->GetErrorExpressions(); } bool bFoundFunctionOutput = false; for (UMaterialExpression* MaterialExpression : Material->GetExpressions()) { if (MaterialExpression->IsA(UMaterialExpressionFunctionOutput::StaticClass())) { bFoundFunctionOutput = true; break; } } if (!bFoundFunctionOutput) { CompileErrors.Add(TEXT("Missing a function output")); FailingExpression.Add(nullptr); } } } else { CompileErrors = MaterialResource->GetCompileErrors(); FailingExpression = MaterialResource->GetErrorExpressions(); } // 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() != EMaterialFunctionUsage::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 estimated virtual texture look-up counts uint32 NumVirtualTextureLookups = MaterialResource->GetEstimatedNumVirtualTextureLookups(); if (NumVirtualTextureLookups > 0) { FString LookupsString = FString::Printf(TEXT("Virtual Texture Lookups (Est.): %u"), NumVirtualTextureLookups); TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(LookupsString, FLinearColor::Yellow))); TSharedRef Line = FTokenizedMessage::Create(EMessageSeverity::Info); Line->AddToken(FTextToken::Create(FText::FromString(LookupsString))); Messages.Add(Line); } const uint32 NumVirtualTextureStacks = MaterialResource->GetNumVirtualTextureStacks(); if (NumVirtualTextureStacks > 0u) { FString VTString = FString::Printf(TEXT("Virtual Texture Stacks: %u"), NumVirtualTextureStacks); TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(VTString, FLinearColor::Yellow))); TSharedRef Line = FTokenizedMessage::Create(EMessageSeverity::Info); Line->AddToken(FTextToken::Create(FText::FromString(VTString))); 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); } TStaticArray LWCFuncUsages = MaterialResource->GetEstimatedLWCFuncUsages(); for (int KindIndex = 0; KindIndex < (int)ELWCFunctionKind::Max; ++KindIndex) { int Usages = LWCFuncUsages[KindIndex]; if (LWCFuncUsages[KindIndex] > 0) { FString Message = FString::Printf(TEXT("LWC %s usages (Est.): %u"), *UEnum::GetDisplayValueAsText((ELWCFunctionKind)KindIndex).ToString(), Usages); TempMaterialInfoList.Add(MakeShareable(new FMaterialInfo(Message, FLinearColor::Yellow))); TSharedRef Line = FTokenizedMessage::Create(EMessageSeverity::Info); Line->AddToken(FTextToken::Create(FText::FromString(Message))); Messages.Add(Line); } } if (FMaterialShaderMap* ShaderMap = MaterialResource->GetGameThreadShaderMap()) { // Add shader count FString ShaderCountString = FString::Printf(TEXT("Shader Count: %u"), ShaderMap->GetShaderNum()); TSharedRef ShaderCountLine = FTokenizedMessage::Create(EMessageSeverity::Info); ShaderCountLine->AddToken(FTextToken::Create(FText::FromString(ShaderCountString))); Messages.Add(ShaderCountLine); } } 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 ); if(FailingExpression.Num() && ensure(FailingExpression.Num() == CompileErrors.Num()) && FailingExpression[ErrorIndex]) { Line->SetMessageLink(FUObjectToken::Create(FailingExpression[ErrorIndex])); } 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->TryInvokeTab(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 (UMaterialExpression* MaterialExpression : Material->GetExpressions()) { if (MaterialExpression->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]); } if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { TSharedPtr TitleBar = FocusedGraphEd->GetTitleBar(); TSharedPtr MaterialTitleBar = StaticCastSharedPtr(TitleBar); 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); MaterialTitleBar->RequestRefresh(); } if (MaterialTitleBar->MaterialInfoList) { MaterialTitleBar->MaterialInfoList->SetVisibility(CompileErrors.IsEmpty() ? EVisibility::Collapsed : EVisibility::Visible); } } // extract material stats MaterialStatsManager->SetMaterial(MaterialForStats); MaterialStatsManager->Update(); } void FMaterialEditor::UpdateGraphNodeStates() { const FMaterialResource* ErrorMaterialResource = PreviewExpression ? ExpressionPreviewMaterial->GetMaterialResource(GMaxRHIFeatureLevel) : Material->GetMaterialResource(GMaxRHIFeatureLevel); bool bUpdatedErrorState = false; bool bToggledVisibleState = bPreviewFeaturesChanged; bool bShowAllNodes = true; TArray VisibleExpressions; FStaticParameterSet StaticSwitchSet; if (bPreviewFeaturesChanged && bPreviewStaticSwitches) { for (UMaterialExpression* Expression : Material->GetExpressions()) { if (UMaterialExpressionStaticSwitchParameter* StaticSwitch = Cast(Expression)) { FStaticSwitchParameter SwitchParam; SwitchParam.Value = StaticSwitch->DefaultValue; SwitchParam.ExpressionGUID = StaticSwitch->ExpressionGUID; StaticSwitchSet.StaticSwitchParameters.Add(SwitchParam); } } } if (bPreviewFeaturesChanged) { Material->GetAllReferencedExpressions(VisibleExpressions, StaticSwitchSet.IsEmpty() ? nullptr : &StaticSwitchSet, NodeFeatureLevel, NodeQualityLevel); if (NodeFeatureLevel != ERHIFeatureLevel::Num || NodeQualityLevel != EMaterialQualityLevel::Num || !StaticSwitchSet.IsEmpty()) { bShowAllNodes = false; } } // Update main material graph and all subgraphs bUpdatedErrorState |= UpdateGraphNodeState(Material->MaterialGraph, ErrorMaterialResource, VisibleExpressions, bShowAllNodes); bPreviewFeaturesChanged = false; if (bUpdatedErrorState || bToggledVisibleState) { // Rebuild the SGraphNodes to display/hide error block if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->NotifyGraphChanged(); } } } bool FMaterialEditor::UpdateGraphNodeState(UEdGraph* Graph, const FMaterialResource* ErrorMaterialResource, TArray& VisibleExpressions, bool bShowAllNodes) { bool bUpdatedErrorState = false; UMaterialGraph* MaterialGraph = CastChecked(Graph); // Have to loop through everything here as there's no way to be notified when the material resource updates for (int32 Index = 0; Index < MaterialGraph->Nodes.Num(); ++Index) { UMaterialGraphNode* MaterialNode = Cast(MaterialGraph->Nodes[Index]); if (MaterialNode) { MaterialNode->bIsPreviewExpression = (PreviewExpression == MaterialNode->MaterialExpression); MaterialNode->bIsErrorExpression = (ErrorMaterialResource != nullptr) && (ErrorMaterialResource->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); } } } } for (UEdGraph* SubGraph : Graph->SubGraphs) { bUpdatedErrorState |= UpdateGraphNodeState(SubGraph, ErrorMaterialResource, VisibleExpressions, bShowAllNodes); } return bUpdatedErrorState; } 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::OnHideConnectors), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsOnHideConnectorsChecked)); ToolkitCommands->MapAction( Commands.ToggleLivePreview, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleLivePreview), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleLivePreviewChecked)); ToolkitCommands->MapAction( Commands.ToggleHideUnrelatedNodes, FExecuteAction::CreateSP(this, &FMaterialEditor::ToggleHideUnrelatedNodes), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsToggleHideUnrelatedNodesChecked)); 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.PromoteToDouble, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteObjects)); ToolkitCommands->MapAction( Commands.PromoteToFloat, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteObjects)); 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.SelectNamedRerouteDeclaration, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectNamedRerouteDeclaration)); ToolkitCommands->MapAction( Commands.SelectNamedRerouteUsages, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectNamedRerouteUsages)); ToolkitCommands->MapAction( Commands.ConvertRerouteToNamedReroute, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertRerouteToNamedReroute)); ToolkitCommands->MapAction( Commands.ConvertNamedRerouteToReroute, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertNamedRerouteToReroute)); 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_Epic, FExecuteAction::CreateSP(this, &FMaterialEditor::SetQualityPreview, EMaterialQualityLevel::Epic), FCanExecuteAction(), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsQualityPreviewChecked, EMaterialQualityLevel::Epic)); 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_Mobile, FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::ES3_1), FCanExecuteAction::CreateSP(this, &FMaterialEditor::IsFeaturePreviewAvailable, ERHIFeatureLevel::ES3_1), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::ES3_1)); ToolkitCommands->MapAction( Commands.FeatureLevel_SM5, FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::SM5), FCanExecuteAction::CreateSP(this, &FMaterialEditor::IsFeaturePreviewAvailable, ERHIFeatureLevel::SM5), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::SM5)); ToolkitCommands->MapAction( Commands.FeatureLevel_SM6, FExecuteAction::CreateSP(this, &FMaterialEditor::SetFeaturePreview, ERHIFeatureLevel::SM6), FCanExecuteAction::CreateSP(this, &FMaterialEditor::IsFeaturePreviewAvailable, ERHIFeatureLevel::SM6), FIsActionChecked::CreateSP(this, &FMaterialEditor::IsFeaturePreviewChecked, ERHIFeatureLevel::SM6)); } 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::OnHideConnectors() { bHideUnusedConnectors = !bHideUnusedConnectors; if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->SetPinVisibility(bHideUnusedConnectors ? SGraphEditor::Pin_HideNoConnection : SGraphEditor::Pin_Show); } } bool FMaterialEditor::IsOnHideConnectorsChecked() const { return bHideUnusedConnectors == true; } 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::ToggleHideUnrelatedNodes() { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (!FocusedGraphEd) { return; } bHideUnrelatedNodes = !bHideUnrelatedNodes; FocusedGraphEd->ResetAllNodesUnrelatedStates(); if (bHideUnrelatedNodes && bSelectRegularNode) { HideUnrelatedNodes(); } else { bLockNodeFadeState = false; bFocusWholeChain = false; } } bool FMaterialEditor::IsToggleHideUnrelatedNodesChecked() const { return bHideUnrelatedNodes == true; } void FMaterialEditor::CollectDownstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes) { for (UEdGraphPin* OutputPin : CurrentNode->Pins) { if (OutputPin->Direction == EGPD_Output) { for (auto& Link : OutputPin->LinkedTo) { UMaterialGraphNode* LinkedNode = Cast(Link->GetOwningNode()); if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) { CollectedNodes.Add(LinkedNode); CollectDownstreamNodes(LinkedNode, CollectedNodes); if (bFocusWholeChain) { CollectUpstreamNodes(LinkedNode, CollectedNodes); } } } } } } void FMaterialEditor::CollectUpstreamNodes(UMaterialGraphNode* CurrentNode, TArray& CollectedNodes) { for (UEdGraphPin* InputPin : CurrentNode->Pins) { if (InputPin->Direction == EGPD_Input) { for (auto& Link : InputPin->LinkedTo) { UMaterialGraphNode* LinkedNode = Cast(Link->GetOwningNode()); if (LinkedNode && !CollectedNodes.Contains(LinkedNode)) { CollectedNodes.Add(LinkedNode); CollectUpstreamNodes(LinkedNode, CollectedNodes); } } } } } void FMaterialEditor::HideUnrelatedNodes() { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (!FocusedGraphEd) { return; } TArray NodesToShow; const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* SelectedNode = Cast(*NodeIt); if (SelectedNode) { NodesToShow.Add(SelectedNode); CollectDownstreamNodes( SelectedNode, NodesToShow ); CollectUpstreamNodes( SelectedNode, NodesToShow ); } } TArray AllNodes = FocusedGraphEd->GetCurrentGraph()->Nodes; TArray CommentNodes; TArray RelatedNodes; for (auto& Node : AllNodes) { // Always draw the root graph node which can't cast to UMaterialGraphNode if (UMaterialGraphNode* GraphNode = Cast(Node)) { if (NodesToShow.Contains(GraphNode)) { Node->SetNodeUnrelated(false); RelatedNodes.Add(Node); } else { Node->SetNodeUnrelated(true); } } else if (UMaterialGraphNode_Comment* CommentNode = Cast(Node)) { CommentNodes.Add(Node); } } FocusedGraphEd->FocusCommentNodes(CommentNodes, RelatedNodes); } void FMaterialEditor::MakeHideUnrelatedNodesOptionsMenu(UToolMenu* Menu) { Menu->bShouldCloseWindowAfterMenuSelection = true; TSharedRef LockNodeStateCheckBox = SNew(SBox) [ SNew(SCheckBox) .IsChecked(bLockNodeFadeState ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) .OnCheckStateChanged(this, &FMaterialEditor::OnLockNodeStateCheckStateChanged) .Style(FAppStyle::Get(), "Menu.CheckBox") .ToolTipText(LOCTEXT("LockNodeStateCheckBoxToolTip", "Lock the current state of all nodes.")) .Content() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(2.0f, 0.0f, 0.0f, 0.0f) [ SNew(STextBlock) .Text(LOCTEXT("LockNodeState", "Lock Node State")) ] ] ]; TSharedRef FocusWholeChainCheckBox = SNew(SBox) [ SNew(SCheckBox) .IsChecked(bFocusWholeChain ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) .OnCheckStateChanged(this, &FMaterialEditor::OnFocusWholeChainCheckStateChanged) .Style(FAppStyle::Get(), "Menu.CheckBox") .ToolTipText(LOCTEXT("FocusWholeChainCheckBoxToolTip", "Focus all nodes in the chain.")) .Content() [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .Padding(2.0f, 0.0f, 0.0f, 0.0f) [ SNew(STextBlock) .Text(LOCTEXT("FocusWholeChain", "Focus Whole Chain")) ] ] ]; FToolMenuSection& OptionsSection = Menu->AddSection("OptionsSection", LOCTEXT("HideUnrelatedOptions", "Hide Unrelated Options")); OptionsSection.AddEntry(FToolMenuEntry::InitMenuEntry("LockNodeStateCheckBox", FUIAction(), LockNodeStateCheckBox)); OptionsSection.AddEntry(FToolMenuEntry::InitMenuEntry("FocusWholeChainCheckBox", FUIAction(), FocusWholeChainCheckBox)); } void FMaterialEditor::OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState) { bLockNodeFadeState = (NewCheckedState == ECheckBoxState::Checked) ? true : false; } void FMaterialEditor::OnFocusWholeChainCheckStateChanged(ECheckBoxState NewCheckedState) { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (!FocusedGraphEd) { return; } bFocusWholeChain = (NewCheckedState == ECheckBoxState::Checked) ? true : false; if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode) { FocusedGraphEd->ResetAllNodesUnrelatedStates(); HideUnrelatedNodes(); } } 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 = 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::OnPromoteObjects() { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() > 0) { const FScopedTransaction Transaction(LOCTEXT("MaterialEditorPromote", "Material Editor: Promote")); 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; UMaterialExpressionVectorParameter* VectorParameterExpression = Cast(CurrentSelectedExpression); UMaterialExpressionDoubleVectorParameter* DoubleVectorParameterExpression = Cast(CurrentSelectedExpression); // Setup the class to convert to UClass* ClassToCreate = nullptr; if (VectorParameterExpression) { ClassToCreate = UMaterialExpressionDoubleVectorParameter::StaticClass(); } else if (DoubleVectorParameterExpression) { ClassToCreate = UMaterialExpressionVectorParameter::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 NewExpression->SetParameterName(CurrentSelectedExpression->GetParameterName()); if (VectorParameterExpression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = FVector4d(VectorParameterExpression->DefaultValue); } else if (DoubleVectorParameterExpression) { bNeedsRefresh = true; CastChecked(NewExpression)->DefaultValue = FLinearColor(DoubleVectorParameterExpression->DefaultValue); } if (bNeedsRefresh) { // Refresh the expression preview if we changed its properties after it was created NewExpression->bNeedToUpdatePreview = true; RefreshExpressionPreview(NewExpression, true); UpdateGenerator(); } NodesToDelete.AddUnique(GraphNode); NodesToSelect.Add(NewGraphNode); } } } } // Delete the replaced nodes DeleteNodes(NodesToDelete); // Select each of the newly converted expressions if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { for (TArray::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter) { FocusedGraphEd->SetNodeSelection(*NodeIter, true); } } } } void FMaterialEditor::OnConvertObjects() { const FGraphPanelSelectionSet SelectedNodes = 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); UMaterialExpressionRuntimeVirtualTextureSample* RuntimeVirtualTextureSampleExpression = Cast(CurrentSelectedExpression); UMaterialExpressionSparseVolumeTextureSample* SparseVolumeTextureSampleExpression = 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 (TextureSampleExpression && TextureSampleExpression->Texture && TextureSampleExpression->Texture->IsA(UTexture2DArray::StaticClass())) { ClassToCreate = UMaterialExpressionTextureSampleParameter2DArray::StaticClass(); } else if (TextureSampleExpression && TextureSampleExpression->Texture && TextureSampleExpression->Texture->IsA(UTextureCubeArray::StaticClass())) { ClassToCreate = UMaterialExpressionTextureSampleParameterCubeArray::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 (RuntimeVirtualTextureSampleExpression) { ClassToCreate = UMaterialExpressionRuntimeVirtualTextureSampleParameter::StaticClass(); } else if (SparseVolumeTextureSampleExpression) { ClassToCreate = UMaterialExpressionSparseVolumeTextureSampleParameter::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 (TextureObjectExpression && !TextureObjectParameterExpression) { bNeedsRefresh = true; UMaterialExpressionTextureObjectParameter* NewTextureObjectParameterExpression = CastChecked(NewExpression); NewTextureObjectParameterExpression->Texture = TextureObjectExpression->Texture; NewTextureObjectParameterExpression->AutoSetSampleType(); NewTextureObjectParameterExpression->IsDefaultMeshpaintTexture = TextureObjectExpression->IsDefaultMeshpaintTexture; } else if (TextureObjectParameterExpression && (!TextureObjectExpression || !TextureSampleExpression)) { bNeedsRefresh = true; UMaterialExpressionTextureObject* NewTextureObjectExpression = CastChecked(NewExpression); NewTextureObjectExpression->Texture = TextureObjectParameterExpression->Texture; NewTextureObjectExpression->AutoSetSampleType(); NewTextureObjectExpression->IsDefaultMeshpaintTexture = TextureObjectParameterExpression->IsDefaultMeshpaintTexture; } else if (RuntimeVirtualTextureSampleExpression) { bNeedsRefresh = true; UMaterialExpressionRuntimeVirtualTextureSampleParameter* NewRuntimeVirtualTextureExpression = CastChecked(NewExpression); NewRuntimeVirtualTextureExpression->VirtualTexture = RuntimeVirtualTextureSampleExpression->VirtualTexture; NewRuntimeVirtualTextureExpression->MaterialType = RuntimeVirtualTextureSampleExpression->MaterialType; NewRuntimeVirtualTextureExpression->MipValueMode = RuntimeVirtualTextureSampleExpression->MipValueMode; NewGraphNode->ReconstructNode(); } else if (SparseVolumeTextureSampleExpression) { bNeedsRefresh = true; UMaterialExpressionSparseVolumeTextureSampleParameter* NewSparseVolumeTextureExpression = CastChecked(NewExpression); NewSparseVolumeTextureExpression->SparseVolumeTexture = SparseVolumeTextureSampleExpression->SparseVolumeTexture; 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 ); UpdateGenerator(); } NodesToDelete.AddUnique(GraphNode); NodesToSelect.Add(NewGraphNode); } } } } // Delete the replaced nodes DeleteNodes(NodesToDelete); // Select each of the newly converted expressions if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { for ( TArray::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter ) { FocusedGraphEd->SetNodeSelection(*NodeIter, true); } } } } void FMaterialEditor::OnConvertTextures() { const FGraphPanelSelectionSet SelectedNodes = 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 if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { for ( TArray::TConstIterator NodeIter(NodesToSelect); NodeIter; ++NodeIter ) { FocusedGraphEd->SetNodeSelection(*NodeIter, true); } } } } void FMaterialEditor::OnSelectNamedRerouteDeclaration() { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { const FGraphPanelSelectionSet SelectedNodes = FocusedGraphEd->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { FocusedGraphEd->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression; UMaterialExpressionNamedRerouteUsage* Usage = Cast(CurrentSelectedExpression); if (Usage && Usage->Declaration) { UEdGraphNode* DeclarationGraphNode = Usage->Declaration->GraphNode; if (DeclarationGraphNode) { FocusedGraphEd->SetNodeSelection(DeclarationGraphNode, true); } } } } FocusedGraphEd->ZoomToFit(true); } } } void FMaterialEditor::OnSelectNamedRerouteUsages() { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { const FGraphPanelSelectionSet SelectedNodes = FocusedGraphEd->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { FocusedGraphEd->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression; UMaterialExpressionNamedRerouteDeclaration* Declaration = Cast(CurrentSelectedExpression); for(UMaterialExpression* Expression : Material->GetExpressions()) { auto* Usage = Cast(Expression); if (Usage && Usage->Declaration == Declaration) { UEdGraphNode* UsageGraphNode = Usage->GraphNode; if (UsageGraphNode) { FocusedGraphEd->SetNodeSelection(UsageGraphNode, true); } } } } } FocusedGraphEd->ZoomToFit(true); } } } void FMaterialEditor::OnConvertRerouteToNamedReroute() { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { const FGraphPanelSelectionSet SelectedNodes = FocusedGraphEd->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { FocusedGraphEd->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode_Knot* GraphNode = Cast(*NodeIt); if (GraphNode) { UEdGraph* Graph = GraphNode->GetGraph(); const FScopedTransaction Transaction(LOCTEXT("ConvertRerouteToNamedReroute", "Convert reroute to named reroute")); Graph->Modify(); auto* Declaration = Cast(FMaterialEditorUtilities::CreateNewMaterialExpression( Graph, UMaterialExpressionNamedRerouteDeclaration::StaticClass(), FVector2D(GraphNode->NodePosX - 50, GraphNode->NodePosY), false, true)); if (!Declaration) { return; } UEdGraphPin* DeclarationInputPin = nullptr; for (auto* Pin : Declaration->GraphNode->GetAllPins()) { if (Pin->Direction == EEdGraphPinDirection::EGPD_Input) { DeclarationInputPin = Pin; break; } } if (!ensure(DeclarationInputPin)) { return; } for (auto* InputPin : GraphNode->GetInputPin()->LinkedTo) { InputPin->MakeLinkTo(DeclarationInputPin); } TArray OutputPins = GraphNode->GetOutputPin()->LinkedTo; OutputPins.Sort([](UEdGraphPin& A, UEdGraphPin& B) { return A.GetOwningNode()->NodePosY < B.GetOwningNode()->NodePosY; }); int Index = -OutputPins.Num() / 2; for (auto* OutputPin : OutputPins) { auto* Usage = Cast(FMaterialEditorUtilities::CreateNewMaterialExpression( Material->MaterialGraph, UMaterialExpressionNamedRerouteUsage::StaticClass(), FVector2D(GraphNode->NodePosX + 50, GraphNode->NodePosY + Index * 50), false, true)); if (Usage) { Usage->Declaration = Declaration; Usage->DeclarationGuid = Declaration->VariableGuid; Usage->GraphNode->GetAllPins()[0]->MakeLinkTo(OutputPin); // usage node has a single pin } Index++; } GraphNode->DestroyNode(); FocusedGraphEd->SetNodeSelection(Declaration->GraphNode, true); } } } } } void FMaterialEditor::OnConvertNamedRerouteToReroute() { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { const FGraphPanelSelectionSet SelectedNodes = FocusedGraphEd->GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { UEdGraph* Graph = GraphNode->GetGraph(); const FScopedTransaction Transaction(LOCTEXT("ConvertNamedRerouteToReroute", "Convert named reroute to reroute")); Graph->Modify(); UMaterialExpression* CurrentSelectedExpression = GraphNode->MaterialExpression; UMaterialExpressionNamedRerouteDeclaration* Declaration = Cast(CurrentSelectedExpression); if (!Declaration) { UMaterialExpressionNamedRerouteUsage* Usage = Cast(CurrentSelectedExpression); if (Usage) { Declaration = Usage->Declaration; } } if (!Declaration) { return; } UEdGraphNode* DeclarationGraphNode = Declaration->GraphNode; FVector2D KnotPosition(DeclarationGraphNode->NodePosX + 50, DeclarationGraphNode->NodePosY); UMaterialExpression* Reroute = FMaterialEditorUtilities::CreateNewMaterialExpression(Graph, UMaterialExpressionReroute::StaticClass(), KnotPosition, false, true); auto KnotGraphNode = CastChecked(Reroute->GraphNode); for (UEdGraphPin* Pin : DeclarationGraphNode->GetAllPins()) { if (Pin->Direction == EEdGraphPinDirection::EGPD_Input) { for (UEdGraphPin* InputPin : Pin->LinkedTo) { KnotGraphNode->GetInputPin()->MakeLinkTo(InputPin); } } if (Pin->Direction == EEdGraphPinDirection::EGPD_Output) { for (UEdGraphPin* OutputPin : Pin->LinkedTo) { KnotGraphNode->GetOutputPin()->MakeLinkTo(OutputPin); } } } DeclarationGraphNode->DestroyNode(); for(UMaterialExpression* Expression : Material->GetExpressions()) { auto* Usage = Cast(Expression); if (Usage && Usage->Declaration == Declaration) { UEdGraphNode* UsageGraphNode = Usage->GraphNode; if (UsageGraphNode) { UEdGraphPin* Pin = Usage->GraphNode->GetAllPins()[0]; // usage node has a single pin for (UEdGraphPin* OutputPin : Pin->LinkedTo) { KnotGraphNode->GetOutputPin()->MakeLinkTo(OutputPin); } UsageGraphNode->DestroyNode(); } } } } } } } } void FMaterialEditor::OnPreviewNode() { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UMaterialGraphNode* GraphNode = Cast(*NodeIt); if (GraphNode) { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->NotifyGraphChanged(); } SetPreviewExpression(GraphNode->MaterialExpression); } } } } void FMaterialEditor::OnToggleRealtimePreview() { const FGraphPanelSelectionSet SelectedNodes = 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 = 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(); for (UEdGraphPin* Pin : CurrentNode->Pins) { if (Pin->Direction == EGPD_Output) { for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex) { UMaterialGraphNode* LinkedNode = Cast(Pin->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); } if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index) { FocusedGraphEd->SetNodeSelection(NodesToSelect[Index], true); } } } void FMaterialEditor::OnSelectUpstreamNodes() { TArray NodesToCheck; TArray CheckedNodes; TArray NodesToSelect; const FGraphPanelSelectionSet SelectedNodes = 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(); for (UEdGraphPin* Pin : CurrentNode->Pins) { if (Pin->Direction == EGPD_Input) { for (int32 LinkIndex = 0; LinkIndex < Pin->LinkedTo.Num(); ++LinkIndex) { UMaterialGraphNode* LinkedNode = Cast(Pin->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); } if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { for (int32 Index = 0; Index < NodesToSelect.Num(); ++Index) { FocusedGraphEd->SetNodeSelection(NodesToSelect[Index], true); } } } void FMaterialEditor::OnForceRefreshPreviews() { ForceRefreshExpressionPreviews(); RefreshPreviewViewport(); } void FMaterialEditor::OnCreateComment() { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { CreateNewMaterialExpressionComment(FocusedGraphEd->GetPasteLocation()); } } void FMaterialEditor::OnCreateComponentMaskNode() { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { CreateNewMaterialExpression(UMaterialExpressionComponentMask::StaticClass(), FocusedGraphEd->GetPasteLocation(), true, false); } } void FMaterialEditor::OnFindInMaterial() { TabManager->TryInvokeTab(FMaterialEditorTabs::FindTabId); FindResults->FocusForUse(); } void FMaterialEditor::OnGraphEditorFocused(const TSharedRef& InGraphEditor) { if (FocusedGraphEdPtr == InGraphEditor) { return; } // Update the graph editor that is currently focused FocusedGraphEdPtr = InGraphEditor; // Refresh navigation history TSharedPtr TitleBar = FocusedGraphEdPtr.Pin()->GetTitleBar(); StaticCastSharedPtr(TitleBar)->RequestRefresh(); // Update the inspector as well, to show selection from the focused graph editor FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (bHideUnrelatedNodes && SelectedNodes.Num() <= 0) { FocusedGraphEdPtr.Pin()->ResetAllNodesUnrelatedStates(); } } void FMaterialEditor::OnGraphEditorBackgrounded(const TSharedRef& InGraphEditor) { FocusedGraphEdPtr = nullptr; } UClass* FMaterialEditor::GetOnPromoteToParameterClass(const UEdGraphPin* TargetPin) const { 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_Anisotropy: case MP_CustomData0: case MP_CustomData1: case MP_AmbientOcclusion: case MP_Refraction: case MP_PixelDepthOffset: case MP_ShadingModel: case MP_OpacityMask: case MP_SurfaceThickness: case MP_Displacement: return UMaterialExpressionScalarParameter::StaticClass(); case MP_WorldPositionOffset: case MP_EmissiveColor: case MP_BaseColor: case MP_SubsurfaceColor: case MP_SpecularColor: case MP_Normal: case MP_Tangent: return UMaterialExpressionVectorParameter::StaticClass(); case MP_FrontMaterial: return nullptr; } } else if (OtherPinNode) { TArrayView ExpressionInputs = OtherPinNode->MaterialExpression->GetInputsView(); 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(); case MCT_Strata: return nullptr; } break; } } } return nullptr; } void FMaterialEditor::OnPromoteToParameter(const FToolMenuContext& InMenuContext) const { UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext(); const UEdGraphPin* TargetPin = NodeContext->Pin; 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, const_cast(TargetPin), NewNodePos)); if (MaterialNode->MaterialExpression->HasAParameterName()) { MaterialNode->MaterialExpression->SetParameterName(TargetPin->PinName); MaterialNode->MaterialExpression->ValidateParameterName(false); } } if (MaterialEditorInstance != nullptr) { MaterialCustomPrimitiveDataWidget->UpdateEditorInstance(MaterialEditorInstance); } } bool FMaterialEditor::OnCanPromoteToParameter(const FToolMenuContext& InMenuContext) const { UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext(); if (!NodeContext) { return false; } const UEdGraphPin* TargetPin = NodeContext->Pin; if (TargetPin && (TargetPin->Direction == EEdGraphPinDirection::EGPD_Input) && (TargetPin->LinkedTo.Num() == 0)) { return GetOnPromoteToParameterClass(TargetPin) != nullptr; } return false; } void FMaterialEditor::OnCreateStrataNodeForPin(const FToolMenuContext& InMenuContext, EStrataNodeForPin NodeForPin) const { UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext(); const UEdGraphPin* TargetPin = NodeContext->Pin; UMaterialGraphNode_Base* PinNode = Cast(TargetPin->GetOwningNode()); const bool bTargetPinIsInput = TargetPin->Direction == EEdGraphPinDirection::EGPD_Input; FMaterialGraphSchemaAction_NewNode Action; Action.MaterialExpressionClass = UMaterialExpressionStrataSlabBSDF::StaticClass(); if (NodeForPin == EStrataNodeForPin::HorizontalMix) { Action.MaterialExpressionClass = UMaterialExpressionStrataHorizontalMixing::StaticClass(); } else if (NodeForPin == EStrataNodeForPin::VerticalLayer) { Action.MaterialExpressionClass = UMaterialExpressionStrataVerticalLayering::StaticClass(); } else if (NodeForPin == EStrataNodeForPin::Weight) { Action.MaterialExpressionClass = UMaterialExpressionStrataWeight::StaticClass(); } check(PinNode); UEdGraph* GraphObj = PinNode->GetGraph(); check(GraphObj); const FScopedTransaction Transaction(LOCTEXT("CreateStrataNode", "Create Strata Node")); GraphObj->Modify(); // Set position of new node to be close to node we clicked on FVector2D NewNodePos; NewNodePos.X = PinNode->NodePosX + (bTargetPinIsInput ? -300 : 300); NewNodePos.Y = PinNode->NodePosY; if (bTargetPinIsInput) { UMaterialGraphNode* NewNode = Cast(Action.PerformAction(GraphObj, const_cast(TargetPin), NewNodePos)); } else { // Link manually UMaterialGraphNode* NewNode = Cast(Action.PerformAction(GraphObj, nullptr, NewNodePos)); TArrayView NewNodeExpressionInputs = NewNode->MaterialExpression->GetInputsView(); // From that direction, the node is never going to be a root node (a root node has no output we can connect from). UMaterialGraphNode* TargetPinNode = Cast(TargetPin->GetOwningNode()); check(NewNodeExpressionInputs.Num() > 0 && TargetPin->SourceIndex < TargetPinNode->MaterialExpression->GetOutputs().Num()); FName TargetPinName = TargetPinNode->MaterialExpression->GetOutputs()[TargetPin->SourceIndex].OutputName; UMaterialEditingLibrary::ConnectMaterialExpressions(TargetPinNode->MaterialExpression, TargetPinName.ToString(), NewNode->MaterialExpression, FString()); Material->MaterialGraph->RebuildGraph(); } if (MaterialEditorInstance != nullptr) { MaterialCustomPrimitiveDataWidget->UpdateEditorInstance(MaterialEditorInstance); } } bool FMaterialEditor::OnCanCreateStrataNodeForPin(const FToolMenuContext& InMenuContext, EStrataNodeForPin NodeForPin) const { UGraphNodeContextMenuContext* NodeContext = InMenuContext.FindContext(); const UEdGraphPin* TargetPin = NodeContext->Pin; UMaterialGraphNode_Root* RootPinNode = Cast(TargetPin->GetOwningNode()); UMaterialGraphNode* OtherPinNode = Cast(TargetPin->GetOwningNode()); if ((TargetPin->Direction == EEdGraphPinDirection::EGPD_Input) && (TargetPin->LinkedTo.Num() == 0)) { if (RootPinNode != nullptr) { EMaterialProperty propertyId = (EMaterialProperty)FCString::Atoi(*TargetPin->PinType.PinSubCategory.ToString()); switch (propertyId) { case MP_FrontMaterial: return true; } } else if (OtherPinNode) { TArrayView ExpressionInputs = OtherPinNode->MaterialExpression->GetInputsView(); 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_Strata: return true; } break; } } } } else if (TargetPin && (TargetPin->Direction == EEdGraphPinDirection::EGPD_Output) && (TargetPin->LinkedTo.Num() == 0) && NodeForPin != EStrataNodeForPin::Slab) { if (OtherPinNode) { const TArray& ExpressionOutputs = OtherPinNode->MaterialExpression->GetOutputs(); check(TargetPin->SourceIndex < ExpressionOutputs.Num()); if (OtherPinNode->MaterialExpression->GetOutputType(TargetPin->SourceIndex) == MCT_Strata) { return true; } } } return false; } FString FMaterialEditor::GetDocLinkForSelectedNode() { FString DocumentationLink; FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (UObject* ObjectInSelection : SelectedNodes) { if (ObjectInSelection != NULL) { UMaterialGraphNode* SelectedGraphNode = Cast(ObjectInSelection); FString DocLink = SelectedGraphNode->GetDocumentationLink(); FString DocExcerpt = SelectedGraphNode->GetDocumentationExcerptName(); DocumentationLink = FEditorClassUtils::GetDocumentationLinkFromExcerpt(DocLink, DocExcerpt); } break; } } return DocumentationLink; } FString FMaterialEditor::GetDocLinkBaseUrlForSelectedNode() { FString DocumentationLinkBaseUrl; FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (SelectedNodes.Num() == 1) { for (UObject* ObjectInSelection : SelectedNodes) { if (ObjectInSelection != NULL) { UMaterialGraphNode* SelectedGraphNode = Cast(ObjectInSelection); FString DocLink = SelectedGraphNode->GetDocumentationLink(); FString DocExcerpt = SelectedGraphNode->GetDocumentationExcerptName(); DocumentationLinkBaseUrl = FEditorClassUtils::GetDocumentationLinkBaseUrlFromExcerpt(DocLink, DocExcerpt); } break; } } return DocumentationLinkBaseUrl; } void FMaterialEditor::OnGoToDocumentation() { FString DocumentationLink = GetDocLinkForSelectedNode(); FString DocumentationLinkBaseUrl = GetDocLinkBaseUrlForSelectedNode(); if (!DocumentationLink.IsEmpty()) { IDocumentation::Get()->Open(DocumentationLink, FDocumentationSourceInfo(TEXT("rightclick_matnode")), DocumentationLinkBaseUrl); } } 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(InAddedAssetData.AssetClassPath); 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::SetNumericParameterDefaultOnDependentMaterials(EMaterialParameterType Type, FName ParameterName, const UE::Shader::FValue& 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->GetCachedExpressionData().FunctionInfos.Num(); FunctionIndex++) { if (CurrentMaterial->GetCachedExpressionData().FunctionInfos[FunctionIndex].Function == MaterialFunction->ParentFunction) { bUpdate = true; break; } } if (bUpdate) { MaterialsToOverride.Add(CurrentMaterial); } } } } else { MaterialsToOverride.Add(OriginalMaterial); } const ERHIFeatureLevel::Type FeatureLevel = GEditor->GetEditorWorldContext().World()->GetFeatureLevel(); for (int32 MaterialIndex = 0; MaterialIndex < MaterialsToOverride.Num(); MaterialIndex++) { UMaterial* CurrentMaterial = MaterialsToOverride[MaterialIndex]; CurrentMaterial->OverrideNumericParameterDefault(Type, 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->OverrideNumericParameterDefault(Type, ParameterName, Value, bOverride, FeatureLevel); } } } } void FMaterialEditor::OnNumericParameterDefaultChanged(class UMaterialExpression* Expression, EMaterialParameterType Type, FName ParameterName, const UE::Shader::FValue& Value) { check(Expression); if (Expression->Material == Material && OriginalMaterial) { SetNumericParameterDefaultOnDependentMaterials(Type, ParameterName, Value, true); OverriddenNumericParametersToRevert.Add(MakeTuple(Type, ParameterName)); } OnParameterDefaultChanged(); } void FMaterialEditor::OnParameterDefaultChanged() { // Brute force all flush virtual textures if this material writes to any runtime virtual texture. if (Material->WritesToRuntimeVirtualTexture()) { ENQUEUE_RENDER_COMMAND(FlushVTCommand)([](FRHICommandListImmediate& RHICmdList) { GetRendererModule().FlushVirtualTextureCache(); }); } } 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_MaterialProperties(const FSpawnTabArgs& Args) { TSharedPtr DetailsTab = SNew(SDockTab) .Label( LOCTEXT("MaterialDetailsTitle", "Details") ) [ MaterialDetailsView.ToSharedRef() ]; if (FocusedGraphEdPtr.IsValid()) { // Since we're initialising, make sure nothing is selected FocusedGraphEdPtr.Pin()->ClearSelectionSet(); } SpawnedDetailsTab = DetailsTab; return DetailsTab.ToSharedRef(); } TSharedRef FMaterialEditor::SpawnTab_Palette(const FSpawnTabArgs& Args) { check( Args.GetTabId() == FMaterialEditorTabs::PaletteTabId ); TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("MaterialPaletteTitle", "Palette")) [ SNew( SBox ) .AddMetaData(FTagMetaData(TEXT("MaterialPalette"))) [ Palette.ToSharedRef() ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_Find(const FSpawnTabArgs& Args) { check(Args.GetTabId() == FMaterialEditorTabs::FindTabId); TSharedRef SpawnedTab = SNew(SDockTab) .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() == FMaterialEditorTabs::PreviewSettingsTabId); TSharedRef InWidget = SNullWidget::NullWidget; if (PreviewViewport.IsValid()) { FAdvancedPreviewSceneModule& AdvancedPreviewSceneModule = FModuleManager::LoadModuleChecked("AdvancedPreviewScene"); InWidget = AdvancedPreviewSceneModule.CreateAdvancedPreviewSceneSettingsWidget(PreviewViewport->GetPreviewScene()); } TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("PreviewSceneSettingsTab", "Preview Scene Settings")) [ SNew(SBox) [ InWidget ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_ParameterDefaults(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("Parameters", "Parameters")) [ SNew(SBox) [ MaterialParametersOverviewWidget.ToSharedRef() ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_CustomPrimitiveData(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("CustomPrimitiveData", "Custom Primitive Data")) [ SNew(SBox) [ MaterialCustomPrimitiveDataWidget.ToSharedRef() ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_LayerProperties(const FSpawnTabArgs& Args) { TSharedRef SpawnedTab = SNew(SDockTab) .Label(LOCTEXT("MaterialLayerPropertiesTitle", "Layer Parameter Preview")) [ SNew(SBorder) .Padding(4) [ MaterialLayersFunctionsInstance.ToSharedRef() ] ]; return SpawnedTab; } TSharedRef FMaterialEditor::SpawnTab_Strata(const FSpawnTabArgs& Args) { check(Args.GetTabId() == FMaterialEditorTabs::StrataTabId); TSharedRef StrataTab = SNew(SDockTab) .Label(LOCTEXT("MaterialSubstrateTabTitle", "Substrate")) [ SNew(SBox) [ StrataWidget.ToSharedRef() ] ]; return StrataTab; } 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->GetExpressionCollection().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; ExpressionPreviewMaterial->bEnableNewHLSLGenerator = Material->IsUsingNewHLSLGenerator(); ExpressionPreviewMaterial->bEnableExecWire = Material->IsUsingControlFlow(); 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->AssignExpressionCollection(Material->GetExpressionCollection()); // 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) { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { if (Node->GetGraph() != FocusedGraphEd->GetCurrentGraph()) { JumpToHyperlink(Node->GetGraph()); // Graph changed so editor changed, use new one to jump FocusedGraphEdPtr.Pin()->JumpToNode(Node, false); } else { FocusedGraphEd->JumpToNode(Node, false); } } } bool FMaterialEditor::FindOpenTabsContainingDocument(const UObject* DocumentID, TArray>& Results) { int32 StartingCount = Results.Num(); TSharedRef Payload = FTabPayload_UObject::Make(DocumentID); DocumentManager->FindMatchingTabs(Payload, /*inout*/ Results); // Did we add anything new? return (StartingCount != Results.Num()); } TSharedPtr FMaterialEditor::OpenDocument(const UObject* DocumentID, FDocumentTracker::EOpenDocumentCause Cause) { TSharedRef Payload = FTabPayload_UObject::Make(DocumentID); TSharedPtr DocumentTab = DocumentManager->OpenDocument(Payload, Cause); return DocumentTab; } void FMaterialEditor::CloseDocumentTab(const UObject* DocumentID) { TSharedRef Payload = FTabPayload_UObject::Make(DocumentID); DocumentManager->CloseTab(Payload); } UMaterialExpression* FMaterialEditor::CreateNewMaterialExpression(UClass* NewExpressionClass, const FVector2D& NodePos, bool bAutoSelect, bool bAutoAssignResource, const UEdGraph* Graph) { check( NewExpressionClass->IsChildOf(UMaterialExpression::StaticClass()) ); TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); UMaterialGraph* ExpressionGraph = Graph ? ToRawPtr(CastChecked(const_cast(Graph))) : ToRawPtr(Material->MaterialGraph); ExpressionGraph->Modify(); if (!IsAllowedExpressionType(NewExpressionClass, MaterialFunction != NULL)) { UE_LOG(LogMaterialEditor, Warning, TEXT("Trying to create a disallowed material expression of type %s."), *NewExpressionClass->GetName()); return NULL; } // Clear the selection if ( bAutoSelect && FocusedGraphEd ) { FocusedGraphEd->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) { ExpressionGraph->AddExpression(NewExpression, bAutoSelect); // Select the new node. if ( bAutoSelect && FocusedGraphEd ) { FocusedGraphEd->SetNodeSelection(NewExpression->GraphNode, true); } } } RegenerateCodeView(); // Update the current preview material. UpdatePreviewMaterial(); Material->MarkPackageDirty(); RefreshExpressionPreviews(); if (FocusedGraphEd) { FocusedGraphEd->NotifyGraphChanged(); } SetMaterialDirty(); return NewExpression; } UMaterialExpressionComposite* FMaterialEditor::CreateNewMaterialExpressionComposite(const FVector2D& NodePos, const UEdGraph* Graph) { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); UMaterialGraph* ExpressionGraph = Graph ? ToRawPtr(CastChecked(const_cast(Graph))) : ToRawPtr(Material->MaterialGraph); ExpressionGraph->Modify(); UMaterialExpressionComposite* NewComposite = nullptr; { const FScopedTransaction Transaction(NSLOCTEXT("UnrealEd", "MaterialEditorNewComposite", "Material Editor: New Composite")); Material->Modify(); UObject* ExpressionOuter = Material; if (MaterialFunction) { ExpressionOuter = MaterialFunction; } UMaterialExpression* NewExpression = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, UMaterialExpressionComposite::StaticClass(), nullptr, NodePos.X, NodePos.Y); NewComposite = Cast(NewExpression); if (NewComposite) { UMaterialExpression* InputPinBase = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, UMaterialExpressionPinBase::StaticClass()); NewComposite->InputExpressions = Cast(InputPinBase); NewComposite->InputExpressions->PinDirection = EGPD_Output; NewComposite->InputExpressions->SubgraphExpression = NewComposite; UMaterialExpression* OutputPinBase = UMaterialEditingLibrary::CreateMaterialExpressionEx(Material, MaterialFunction, UMaterialExpressionPinBase::StaticClass()); NewComposite->OutputExpressions = Cast(OutputPinBase); NewComposite->OutputExpressions->PinDirection = EGPD_Input; NewComposite->OutputExpressions->SubgraphExpression = NewComposite; // Create graph node, subgraph, and pinbase graph nodes { ExpressionGraph->AddExpression(NewComposite, true); UMaterialGraphNode_Composite* CompositeNode = CastChecked(NewComposite->GraphNode); CompositeNode->BoundGraph = ExpressionGraph->AddSubGraph(NewComposite); CompositeNode->BoundGraph->Rename(*CastChecked(CompositeNode->MaterialExpression)->SubgraphName); CompositeNode->BoundGraph->AddExpression(InputPinBase, false); CompositeNode->BoundGraph->AddExpression(OutputPinBase, false); } if (FocusedGraphEd) { FocusedGraphEd->ClearSelectionSet(); FocusedGraphEd->SetNodeSelection(NewComposite->GraphNode, true); } } } RefreshExpressionPreviews(); if (FocusedGraphEd) { FocusedGraphEd->NotifyGraphChanged(); } SetMaterialDirty(); return NewComposite; } UMaterialExpressionComment* FMaterialEditor::CreateNewMaterialExpressionComment(const FVector2D& NodePos, const UEdGraph* Graph) { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); UMaterialGraph* ExpressionGraph = Graph ? ToRawPtr(CastChecked(const_cast(Graph))) : ToRawPtr(Material->MaterialGraph); ExpressionGraph->Modify(); 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->GetExpressionCollection().AddComment( NewComment ); FSlateRect Bounds; if (FocusedGraphEd && FocusedGraphEd->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(); ExpressionGraph->AddComment(NewComment, true); // Select the new comment. if (FocusedGraphEd) { FocusedGraphEd->ClearSelectionSet(); FocusedGraphEd->SetNodeSelection(NewComment->GraphNode, true); } } Material->MarkPackageDirty(); if (FocusedGraphEd) { FocusedGraphEd->NotifyGraphChanged(); } SetMaterialDirty(); return NewComment; } void FMaterialEditor::ForceRefreshExpressionPreviews() { // Initialize expression previews. const bool bOldAlwaysRefreshAllPreviews = bAlwaysRefreshAllPreviews; bAlwaysRefreshAllPreviews = true; RefreshExpressionPreviews(); bAlwaysRefreshAllPreviews = bOldAlwaysRefreshAllPreviews; } void FMaterialEditor::AddToSelection(UMaterialExpression* Expression) { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->SetNodeSelection(Expression->GraphNode, true); } } void FMaterialEditor::JumpToExpression(UMaterialExpression* Expression) { check(Expression); UEdGraphNode* ExpressionNode = nullptr; // Note: 'Expression' may be from a serialized material with no graph, we compare to our material with a graph if this occurs if (Expression->GraphNode) { ExpressionNode = Expression->GraphNode; } else if (Expression->bIsParameterExpression) { TArray* GraphExpressions = Material->EditorParameters.Find(Expression->GetParameterName()); if (GraphExpressions && GraphExpressions->Num() == 1) { ExpressionNode = GraphExpressions->Last()->GraphNode; } else { UMaterialExpressionParameter* GraphExpression = Material->FindExpressionByGUID(Expression->GetParameterExpressionId()); ExpressionNode = GraphExpression ? ToRawPtr(GraphExpression->GraphNode) : nullptr; } } else if (UMaterialExpressionFunctionOutput* ExpressionOutput = Cast(Expression)) { TArray FunctionOutputExpressions; Material->GetAllFunctionOutputExpressions(FunctionOutputExpressions); UMaterialExpressionFunctionOutput** GraphExpression = FunctionOutputExpressions.FindByPredicate( [&](const UMaterialExpressionFunctionOutput* GraphExpressionOutput) { return GraphExpressionOutput->Id == ExpressionOutput->Id; }); ExpressionNode = GraphExpression ? ToRawPtr((*GraphExpression)->GraphNode) : nullptr; } JumpToNode(ExpressionNode); } void FMaterialEditor::SelectAllNodes() { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->SelectAllNodes(); } } bool FMaterialEditor::CanSelectAllNodes() const { return FocusedGraphEdPtr.IsValid(); } void FMaterialEditor::DeleteSelectedNodes() { TArray NodesToDelete; const FGraphPanelSelectionSet SelectedNodes = 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") ); DeleteNodesInternal(NodesToDelete, bHaveExpressionsToDelete, bPreviewExpressionDeleted); Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); } // ScopedTransaction // Deselect all expressions and comments. if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->ClearSelectionSet(); FocusedGraphEd->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 = 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() { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (!FocusedGraphEd) { return; } // Cache off the old selection const FGraphPanelSelectionSet OldSelectedNodes = GetSelectedNodes(); // Clear the selection and only select the nodes that can be duplicated FGraphPanelSelectionSet RemainingNodes; FocusedGraphEd->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if ((Node != NULL) && Node->CanDuplicateNode()) { FocusedGraphEd->SetNodeSelection(Node, true); } else { RemainingNodes.Add(Node); } } // Delete the duplicatable nodes DeleteSelectedNodes(); // Reselect whatever's left from the original selection after the deletion FocusedGraphEd->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(RemainingNodes); SelectedIter; ++SelectedIter) { if (UEdGraphNode* Node = Cast(*SelectedIter)) { FocusedGraphEd->SetNodeSelection(Node, true); } } } void FMaterialEditor::DeleteNodesInternal(const TArray& NodesToDelete, bool& bHaveExpressionsToDelete, bool& bPreviewExpressionDeleted) { Material->Modify(); for (int32 Index = 0; Index < NodesToDelete.Num(); ++Index) { if (NodesToDelete[Index]->CanUserDeleteNode()) { // If this is a user-selected pinbase, don't allow the delete to pass if (Cast(NodesToDelete[Index]) && GetSelectedNodes().Contains(NodesToDelete[Index])) { continue; } if (UMaterialGraphNode* GraphNode = Cast(NodesToDelete[Index])) { // Break all node links first so that we don't update the material before deleting GraphNode->Modify(); GraphNode->BreakAllNodeLinks(); UMaterialExpression* MaterialExpression = GraphNode->MaterialExpression; bHaveExpressionsToDelete = true; DestroyColorPicker(); if( PreviewExpression == MaterialExpression ) { // The expression being previewed is also being deleted bPreviewExpressionDeleted = true; } if (UMaterialGraphNode_PinBase* PinBaseNode = Cast(GraphNode)) { UMaterialExpressionPinBase* PinBase = CastChecked(MaterialExpression); PinBase->DeleteReroutePins(); } if (UMaterialGraphNode_Composite* CompositeNode = Cast(GraphNode)) { CloseDocumentTab(CompositeNode->BoundGraph); UMaterialExpressionComposite* SubgraphComposite = CastChecked(MaterialExpression); SubgraphComposite->Modify(); // Remove all subgraph nodes, note that composites remove their subgraph in DestroyNode const TArray SubgraphNodesToDelete = CompositeNode->BoundGraph->Nodes; DeleteNodesInternal(SubgraphNodesToDelete, bHaveExpressionsToDelete, bPreviewExpressionDeleted); } if(MaterialExpression) { MaterialExpression->Modify(); Material->GetExpressionCollection().RemoveExpression( MaterialExpression ); Material->RemoveExpressionParameter(MaterialExpression); // Make sure the deleted expression is caught by gc MaterialExpression->MarkAsGarbage(); } } else if (UMaterialGraphNode_Comment* CommentNode = Cast(NodesToDelete[Index])) { CommentNode->Modify(); CommentNode->BreakAllNodeLinks(); CommentNode->MaterialExpressionComment->Modify(); Material->GetExpressionCollection().RemoveComment( CommentNode->MaterialExpressionComment ); } // Now that we are done with the node, remove it FBlueprintEditorUtils::RemoveNode(NULL, NodesToDelete[Index], true); } } } void FMaterialEditor::CopySelectedNodes() { // Export the selected nodes and place the text on the clipboard const FGraphPanelSelectionSet SelectedNodes = 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 = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if ((Node != NULL) && Node->CanDuplicateNode()) { return true; } } return false; } void FMaterialEditor::PasteNodes() { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { PasteNodesHere(FocusedGraphEd->GetPasteLocation(), FocusedGraphEd->GetCurrentGraph()); } } void FMaterialEditor::PostPasteMaterialExpression(UMaterialExpression* NewExpression) { // Deep copy subgraph expressions if (UMaterialExpressionComposite* Composite = Cast(NewExpression)) { UMaterialGraphNode_Composite* CompositeNode = Cast(Composite->GraphNode); DeepCopyExpressions(CompositeNode->BoundGraph, Composite); // We just updated all our child expressions, reconstruct node. Composite->GraphNode->ReconstructNode(); } else { // Give new expression a different Guid from the old one after pasting NewExpression->UpdateMaterialExpressionGuid(true, true); } // 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) { NewExpression->GraphNode->Pins.Empty(); } } } void FMaterialEditor::PasteNodesHere(const FVector2D& Location, const class UEdGraph* Graph) { // Undo/Redo support const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "MaterialEditorPaste", "Material Editor: Paste") ); Material->MaterialGraph->Modify(); Material->Modify(); UMaterialGraph* ExpressionGraph = Graph ? ToRawPtr(CastChecked(const_cast(Graph))) : ToRawPtr(Material->MaterialGraph); ExpressionGraph->Modify(); // Clear the selection set (newly pasted stuff will be selected) if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->ClearSelectionSet(); } // Grab the text to paste from the clipboard. FString TextToImport; FPlatformApplicationMisc::ClipboardPaste(TextToImport); // Import the nodes TSet PastedNodes; FEdGraphUtilities::ImportNodesFromText(ExpressionGraph, 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; } TArray NewMaterialExpressions; 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; NewExpression->SubgraphExpression = ExpressionGraph->SubgraphExpression; // Make sure the param name is valid after the paste if (NewExpression->HasAParameterName()) { NewExpression->ValidateParameterName(); } NewMaterialExpressions.Add(NewExpression); Material->GetExpressionCollection().AddExpression(NewExpression); ensure(NewExpression->GraphNode == GraphNode); PostPasteMaterialExpression(NewExpression); } else if (UMaterialGraphNode_Comment* CommentNode = Cast(Node)) { if (CommentNode->MaterialExpressionComment) { CommentNode->MaterialDirtyDelegate = Material->MaterialGraph->MaterialDirtyDelegate; CommentNode->MaterialExpressionComment->Material = Material; CommentNode->MaterialExpressionComment->Function = MaterialFunction; CommentNode->MaterialExpressionComment->SubgraphExpression = ExpressionGraph->SubgraphExpression; Material->GetExpressionCollection().AddComment(CommentNode->MaterialExpressionComment); } } // Select the newly pasted stuff if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->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(); } for (auto* NewExpression : NewMaterialExpressions) { // For named reroute fixup: once all nodes are added to Material->Expressions, and that all their Material property are valid NewExpression->PostCopyNode(NewMaterialExpressions); } UpdateMaterialAfterGraphChange(); // Update UI if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->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::UpdateStrataTopologyPreview() { if (Strata::IsStrataEnabled()) { // Update all Strata node which have a preview. for (int32 Index = 0; Index < Material->MaterialGraph->Nodes.Num(); ++Index) { UMaterialGraphNode* MaterialNode = Cast(Material->MaterialGraph->Nodes[Index]); if (MaterialNode && MaterialNode->MaterialExpression && MaterialNode->MaterialExpression->IsA(UMaterialExpressionStrataBSDF::StaticClass())) { UEdGraph* Graph = MaterialNode->GetGraph(); if (Graph) { Graph->NotifyGraphChanged(); } } } } } void FMaterialEditor::UpdateMaterialAfterGraphChange() { FlushRenderingCommands(); Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); // Update the current preview material. UpdatePreviewMaterial(); Material->MarkPackageDirty(); RegenerateCodeView(); RefreshExpressionPreviews(); SetMaterialDirty(); UpdateGenerator(); if (NodeFeatureLevel != ERHIFeatureLevel::Num || NodeQualityLevel != EMaterialQualityLevel::Num || bPreviewStaticSwitches) { bPreviewFeaturesChanged = true; } if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode) { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->ResetAllNodesUnrelatedStates(); } HideUnrelatedNodes(); } Material->MaterialGraph->UpdatePinTypes(); UpdateStrataTopologyPreview(); } void FMaterialEditor::JumpToHyperlink(const UObject* ObjectReference) { if (const UEdGraphNode* Node = Cast(ObjectReference)) { JumpToNode(Node); } else if (const UEdGraph* Graph = Cast(ObjectReference)) { // Navigating into things should re-use the current tab when it makes sense FDocumentTracker::EOpenDocumentCause OpenMode = FDocumentTracker::OpenNewDocument; if ((Graph == Material->MaterialGraph) || Cast(Graph->GetOuter())) { OpenMode = FDocumentTracker::NavigatingCurrentDocument; } else { // Walk up the outer chain to see if any tabs have a parent of this document open for edit, and if so // we should reuse that one and drill in deeper instead for (UObject* WalkPtr = const_cast(Graph); WalkPtr != nullptr; WalkPtr = WalkPtr->GetOuter()) { TArray< TSharedPtr > TabResults; if (FindOpenTabsContainingDocument(WalkPtr, /*out*/ TabResults)) { // See if the parent was active bool bIsActive = false; for (TSharedPtr Tab : TabResults) { if (Tab->IsActive()) { bIsActive = true; break; } } if (bIsActive) { OpenMode = FDocumentTracker::NavigatingCurrentDocument; break; } } } } // Force it to open in a new document if shift is pressed const bool bIsShiftPressed = FSlateApplication::Get().GetModifierKeys().IsShiftDown(); if (bIsShiftPressed) { auto PayloadAlreadyOpened = [&]() { TArray< TSharedPtr > GraphEditorTabs; DocumentManager->FindAllTabsForFactory(GraphEditorTabFactoryPtr, /*out*/ GraphEditorTabs); for (TSharedPtr& GraphEditorTab : GraphEditorTabs) { TSharedRef Editor = StaticCastSharedRef((GraphEditorTab)->GetContent()); if (Editor->GetCurrentGraph() == Graph) { return true; } } return false; }; OpenMode = PayloadAlreadyOpened() ? FDocumentTracker::RestorePreviousDocument : FDocumentTracker::ForceOpenNewDocument; } // Open the document OpenDocument(Graph, OpenMode); } } int32 FMaterialEditor::GetNumberOfSelectedNodes() const { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { return FocusedGraphEd->GetSelectedNodes().Num(); } return 0; } FGraphPanelSelectionSet FMaterialEditor::GetSelectedNodes() const { FGraphPanelSelectionSet CurrentSelection; if (FocusedGraphEdPtr.IsValid()) { CurrentSelection = FocusedGraphEdPtr.Pin()->GetSelectedNodes(); } return CurrentSelection; } void FMaterialEditor::GetBoundsForNode(const UEdGraphNode* InNode, class FSlateRect& OutRect, float InPadding) const { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->GetBoundsForNode(InNode, OutRect, InPadding); } } FMatExpressionPreview* FMaterialEditor::GetExpressionPreview(UMaterialExpression* InExpression) { bool bNewlyCreated; return GetExpressionPreview(InExpression, bNewlyCreated); } void FMaterialEditor::UndoGraphAction() { FlushRenderingCommands(); int32 NumExpressions = Material->GetExpressions().Num(); GEditor->UndoTransaction(); if(NumExpressions != Material->GetExpressions().Num()) { Material->BuildEditorParameterList(); } } void FMaterialEditor::RedoGraphAction() { FlushRenderingCommands(); int32 NumExpressions = Material->GetExpressions().Num(); GEditor->RedoTransaction(); if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { // @TODO Find a more coherent check for this, rather than rely on sage knowledge that the schema won't exist. // If our previous transaction created the current graph, it won't have a schema after undo. if (!IsValidChecked(FocusedGraphEd->GetCurrentGraph())) { CloseDocumentTab(FocusedGraphEd->GetCurrentGraph()); DocumentManager->CleanInvalidTabs(); DocumentManager->RefreshAllTabs(); GetTabManager()->TryInvokeTab(FMaterialEditorTabs::GraphEditor); } // Active graph can change above, re-acquire ptr FocusedGraphEdPtr.Pin()->NotifyGraphChanged(); } if(NumExpressions != Material->GetExpressions().Num()) { Material->BuildEditorParameterList(); } UpdateGenerator(); } void FMaterialEditor::OnCollapseNodes() { const UMaterialGraphSchema* Schema = GetDefault(); // Does the selection set contain anything that is legal to collapse? TSet CollapsableNodes; const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UEdGraphNode* SelectedNode = Cast(*NodeIt)) { if (Schema->CanEncapuslateNode(*SelectedNode)) { CollapsableNodes.Add(SelectedNode); } } } // Sort for deterministic pin ordering, unaffected by selection order. CollapsableNodes.StableSort([](const UEdGraphNode& A, const UEdGraphNode& B) { if (A.NodePosY == B.NodePosY) { return A.NodePosX > B.NodePosX; } return A.NodePosY < B.NodePosY; }); // Collapse them if (CollapsableNodes.Num()) { const FScopedTransaction Transaction(FGraphEditorCommands::Get().CollapseNodes->GetDescription()); Material->Modify(); Material->MaterialGraph->Modify(); CollapseNodes(CollapsableNodes); } } bool FMaterialEditor::CanCollapseNodes() const { // Does the selection set contain anything that is legal to collapse? const UMaterialGraphSchema* Schema = GetDefault(); FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (UEdGraphNode* Node = Cast(*NodeIt)) { if (Schema->CanEncapuslateNode(*Node)) { return true; } } } return false; } void FMaterialEditor::OnExpandNodes() { const FScopedTransaction Transaction(FGraphEditorCommands::Get().ExpandNodes->GetLabel()); Material->Modify(); Material->MaterialGraph->Modify(); TSet ExpandedNodes; TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); // Expand selected nodes into the focused graph context. const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); if (FocusedGraphEd) { FocusedGraphEd->ClearSelectionSet(); } for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { ExpandedNodes.Empty(); bool bExpandedNodesNeedUniqueGuid = true; DocumentManager->CleanInvalidTabs(); if (UMaterialGraphNode_Composite* SelectedCompositeNode = Cast(*NodeIt)) { // No need to assign unique GUIDs since the source graph will be removed. bExpandedNodesNeedUniqueGuid = false; // Expand the composite node back into the world UEdGraph* SourceGraph = SelectedCompositeNode->BoundGraph; ExpandNode(SelectedCompositeNode, SourceGraph, /*inout*/ ExpandedNodes); FBlueprintEditorUtils::RemoveGraph(nullptr, SourceGraph, EGraphRemoveFlags::None); SourceGraph->MarkAsGarbage(); } UEdGraphNode* SourceNode = CastChecked(*NodeIt); check(SourceNode); MoveNodesToAveragePos(ExpandedNodes, FVector2D(SourceNode->NodePosX, SourceNode->NodePosY), bExpandedNodesNeedUniqueGuid); } UMaterialExpression* SubgraphExpression = FocusedGraphEd ? ToRawPtr(Cast(FocusedGraphEd->GetCurrentGraph())->SubgraphExpression) : ToRawPtr(Material->MaterialGraph->SubgraphExpression); for (UEdGraphNode* ExpandedNode : ExpandedNodes) { if (UMaterialGraphNode* MaterialNode = Cast(ExpandedNode)) { MaterialNode->MaterialExpression->Modify(); MaterialNode->MaterialExpression->SubgraphExpression = SubgraphExpression; } else if (UMaterialGraphNode_Comment* CommentNode = Cast(ExpandedNode)) { CommentNode->MaterialExpressionComment->Modify(); CommentNode->MaterialExpressionComment->SubgraphExpression = SubgraphExpression; } } UpdateMaterialAfterGraphChange(); } bool FMaterialEditor::CanExpandNodes() const { // Does the selection set contain any composite nodes that are legal to expand? const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { if (Cast(*NodeIt)) { return true; } } return false; } void FMaterialEditor::OnAlignTop() { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->OnAlignTop(); } } void FMaterialEditor::OnAlignMiddle() { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->OnAlignMiddle(); } } void FMaterialEditor::OnAlignBottom() { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->OnAlignBottom(); } } void FMaterialEditor::OnAlignLeft() { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->OnAlignLeft(); } } void FMaterialEditor::OnAlignCenter() { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->OnAlignCenter(); } } void FMaterialEditor::OnAlignRight() { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->OnAlignRight(); } } void FMaterialEditor::OnStraightenConnections() { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->OnStraightenConnections(); } } void FMaterialEditor::OnDistributeNodesH() { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->OnDistributeNodesH(); } } void FMaterialEditor::OnDistributeNodesV() { if (FocusedGraphEdPtr.IsValid()) { FocusedGraphEdPtr.Pin()->OnDistributeNodesV(); } } void FMaterialEditor::PostUndo(bool bSuccess) { if (bSuccess) { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->ClearSelectionSet(); } // After an undo operation, objects material expressions reference have their back-references to the material expressions // cleared (the serializer sets it to null). Loop through all material expressions in the material and fix-up these secondary references. for (UMaterialExpression* MaterialExpression : Material->GetExpressions()) { MaterialExpression->Material = Material; UMaterialGraphNode* GraphNode = Cast(MaterialExpression->GraphNode); if (GraphNode) { GraphNode->MaterialExpression = MaterialExpression; } } Material->BuildEditorParameterList(); // Update the current preview material. UpdatePreviewMaterial(); UpdatePreviewViewportsVisibility(); RefreshExpressionPreviews(); // Remove any tabs are that are pending kill or otherwise invalid UObject pointers. bool bNeedOpenGraphEditor = false; if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { // @TODO Find a more coherent check for this, rather than rely on sage knowledge that the schema won't exist. // If our previous transaction created the current graph, it won't have a schema after undo. if (!FocusedGraphEd->GetCurrentGraph()->GetSchema()) { CloseDocumentTab(FocusedGraphEd->GetCurrentGraph()); DocumentManager->CleanInvalidTabs(); DocumentManager->RefreshAllTabs(); GetTabManager()->TryInvokeTab(FMaterialEditorTabs::GraphEditor); } // Active graph can change above, re-acquire ptr FocusedGraphEdPtr.Pin()->NotifyGraphChanged(); } SetMaterialDirty(); UpdateGenerator(); FSlateApplication::Get().DismissAllMenus(); } } void FMaterialEditor::NotifyPreChange(FProperty* PropertyAboutToChange) { check( !ScopedTransaction ); ScopedTransaction = new FScopedTransaction( NSLOCTEXT("UnrealEd", "MaterialEditorEditProperties", "Material Editor: Edit Properties") ); FlushRenderingCommands(); } void FMaterialEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged) { check( ScopedTransaction ); if ( PropertyThatChanged ) { MaterialCustomPrimitiveDataWidget->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, bEnableExecWire)) { Material->MaterialGraph->RebuildGraph(); } 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 ); SetPreviewMaterial(Material); if (ExpressionPreviewMaterial) { if (Material->IsUIMaterial()) { ExpressionPreviewMaterial->MaterialDomain = MD_UI; } else { ExpressionPreviewMaterial->MaterialDomain = MD_Surface; } SetPreviewMaterial(ExpressionPreviewMaterial); } UpdatePreviewViewportsVisibility(); } else if (bPreviewStaticSwitches && PropertyThatChanged->GetOwnerClass() == UMaterialExpressionStaticBoolParameter::StaticClass() && NameOfPropertyThatChanged == GET_MEMBER_NAME_CHECKED(UMaterialExpressionStaticBoolParameter, DefaultValue)) { bPreviewFeaturesChanged = true; } FGraphPanelSelectionSet SelectedNodes = 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 if (PropertyThatChanged->IsA()) { // Do nothing to the expression if we are just changing the label } 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(UMaterialExpressionComment, bColorCommentBubble) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionComment, bGroupMode) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionComment, bCommentBubbleVisible_InDetailsPanel) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpression, Desc) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionVectorParameter, ChannelNames) && PropertyName != GET_MEMBER_NAME_CHECKED(UMaterialExpressionTextureSampleParameter, ChannelNames)) { // Update the current preview material. UpdatePreviewMaterial(); RefreshExpressionPreviews(); RegenerateCodeView(); } GetDefault()->ForceVisualizationCacheClear(); } delete ScopedTransaction; ScopedTransaction = NULL; Material->MarkPackageDirty(); SetMaterialDirty(); UpdateGenerator(); } 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; Material->UpdateCachedExpressionData(); 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. FMaterial::DeferredDeleteArray(ExpressionPreviews); for (UMaterialExpression* MaterialExpression : Material->GetExpressions()) { UMaterialGraphNode* GraphNode = Cast(MaterialExpression->GraphNode); if (GraphNode) { GraphNode->InvalidatePreviewMaterialDelegate.ExecuteIfBound(); } } } else { // Only refresh expressions that are marked for realtime update. for (UMaterialExpression* MaterialExpression : Material->GetExpressions()) { 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 (UMaterialExpression* MaterialExpression : Material->GetExpressions()) { 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 we don't hold any other references before queuing the deferred delete as it may execute immediately (e.g. -onethread, or just very fast RT). // There's no danger in releasing the last reference to FMaterial as this doesn't delete it anyway, it needs to be deleted manually via DeferredDelete. ExpressionPreviews.RemoveAt(PreviewIndex); FMaterial::DeferredDelete(ExpressionPreview); ExpressionPreview = nullptr; 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 && !MaterialExpression->IsA()) { 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, EMaterialShaderPrecompileMode::None); } return Preview; } return NULL; } void FMaterialEditor::OnColorPickerCommitted(FLinearColor LinearColor, TWeakObjectPtr ColorPickerObject) { if (UObject* Object = ColorPickerObject.Get()) { // Begin a property edit transaction. if (GEditor) { GEditor->BeginTransaction(LOCTEXT("ModifyColorPicker", "Modify Color Picker Value")); } NotifyPreChange(NULL); Object->PreEditChange(NULL); FName PropertyName; if (UMaterialExpressionConstant3Vector* Constant3Expression = Cast(Object)) { Constant3Expression->Constant = LinearColor; PropertyName = TEXT("Constant"); } else if (UMaterialExpressionConstant4Vector* Constant4Expression = Cast(Object)) { Constant4Expression->Constant = LinearColor; PropertyName = TEXT("Constant"); } else if (UMaterialExpressionFunctionInput* InputExpression = Cast(Object)) { InputExpression->PreviewValue = LinearColor; } else if (UMaterialExpressionVectorParameter* VectorExpression = Cast(Object)) { VectorExpression->DefaultValue = LinearColor; PropertyName = TEXT("DefaultValue"); } else { checkf(false, TEXT("The expression type is supported in OnNodeDoubleClicked but not in OnColorPickerCommitted")); } Object->MarkPackageDirty(); FProperty* ColorPickerProperty = PropertyName.IsNone() ? nullptr : Object->GetClass()->FindPropertyByName(PropertyName); FPropertyChangedEvent Event(ColorPickerProperty); Object->PostEditChangeProperty(Event); NotifyPostChange(NULL, NULL); if (GEditor) { GEditor->EndTransaction(); } RefreshExpressionPreviews(); MaterialCustomPrimitiveDataWidget->UpdateEditorInstance(MaterialEditorInstance); } } /** Create new tab for the supplied graph - don't call this directly, instead call OpenDocument to track history.*/ TSharedRef FMaterialEditor::CreateGraphEditorWidget(TSharedRef InTabInfo, class UEdGraph* InGraph) { check((InGraph != nullptr) && Cast(InGraph)); if (!GraphEditorCommands) { 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 ) ); GraphEditorCommands->MapAction(FGenericCommands::Get().Rename, FExecuteAction::CreateSP(this, &FMaterialEditor::OnRenameNode), FCanExecuteAction::CreateSP(this, &FMaterialEditor::CanRenameNodes) ); // 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().PromoteToDouble, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteObjects) ); GraphEditorCommands->MapAction(FMaterialEditorCommands::Get().PromoteToFloat, FExecuteAction::CreateSP(this, &FMaterialEditor::OnPromoteObjects) ); 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().SelectNamedRerouteDeclaration, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectNamedRerouteDeclaration) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().SelectNamedRerouteUsages, FExecuteAction::CreateSP(this, &FMaterialEditor::OnSelectNamedRerouteUsages) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertRerouteToNamedReroute, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertRerouteToNamedReroute) ); GraphEditorCommands->MapAction( FMaterialEditorCommands::Get().ConvertNamedRerouteToReroute, FExecuteAction::CreateSP(this, &FMaterialEditor::OnConvertNamedRerouteToReroute) ); 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) ); // Collapse Node Commands GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CollapseNodes, FExecuteAction::CreateSP( this, &FMaterialEditor::OnCollapseNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanCollapseNodes ) ); GraphEditorCommands->MapAction( FGraphEditorCommands::Get().ExpandNodes, FExecuteAction::CreateSP( this, &FMaterialEditor::OnExpandNodes ), FCanExecuteAction::CreateSP( this, &FMaterialEditor::CanExpandNodes ), FIsActionChecked(), FIsActionButtonVisible::CreateSP( this, &FMaterialEditor::CanExpandNodes ) ); // 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(InGraph)); // Create the title bar widget TSharedPtr TitleBarWidget = SNew(SMaterialEditorTitleBar) .EdGraphObj(InGraph) .TitleText(this, &FMaterialEditor::GetOriginalObjectName) .OnDifferentGraphCrumbClicked(this, &FMaterialEditor::OnChangeBreadCrumbGraph) .HistoryNavigationWidget(InTabInfo->CreateHistoryNavigationWidget()) .MaterialInfoList(&MaterialInfoList); return SNew(SGraphEditor) .AdditionalCommands(GraphEditorCommands) .IsEditable(true) .TitleBar(TitleBarWidget) .Appearance(this, &FMaterialEditor::GetGraphAppearance) .GraphToEdit(InGraph) .GraphEvents(InEvents) .ShowGraphStateOverlay(false) .OnNavigateHistoryBack(FSimpleDelegate::CreateSP(this, &FMaterialEditor::NavigateTab, FDocumentTracker::NavigateBackwards)) .OnNavigateHistoryForward(FSimpleDelegate::CreateSP(this, &FMaterialEditor::NavigateTab, FDocumentTracker::NavigateForwards)) .AssetEditorToolkit(this->AsShared()); } 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"); } if (Strata::IsStrataEnabled()) { UMaterial* MaterialForStats = this->bStatsFromPreviewMaterial ? this->Material : this->OriginalMaterial; const FMaterialResource* MaterialResource = MaterialForStats->GetMaterialResource(GMaxRHIFeatureLevel); if (MaterialResource) { FString MaterialDescription; FMaterialShaderMap* ShaderMap = MaterialResource->GetGameThreadShaderMap(); if (ShaderMap) { const FStrataMaterialCompilationOutput& CompilationOutput = ShaderMap->GetStrataMaterialCompilationOutput(); if (CompilationOutput.bMaterialOutOfBudgetHasBeenSimplified) { AppearanceInfo.WarningText = LOCTEXT("AppearanceWarningText_Material", "Substrate material was out of budget and has been simplified."); } } } } return AppearanceInfo; } void FMaterialEditor::DeepCopyExpressions(UMaterialGraph* CopyGraph, UMaterialExpression* NewSubgraphExpression) { if (!CopyGraph || !NewSubgraphExpression) { return; } CopyGraph->Modify(); CopyGraph->SubgraphExpression = NewSubgraphExpression; // Duplicate subnodes auto DuplicateExpression = [&](auto* Expression) { using ExpressionType = typename std::remove_pointer::type; return Cast(UMaterialEditingLibrary::DuplicateMaterialExpression(Material, MaterialFunction, Expression)); }; for (UEdGraphNode* Node : CopyGraph->Nodes) { if (UMaterialGraphNode* MaterialNode = Cast(Node)) { MaterialNode->Modify(); MaterialNode->Rename(/*NewName=*/ NULL, /*NewOuter=*/ CopyGraph); if (UMaterialExpressionPinBase* PinBase = Cast(MaterialNode->MaterialExpression)) { UMaterialExpressionPinBase* OldPinBase = PinBase; UMaterialExpressionPinBase* NewPinBase = DuplicateExpression(OldPinBase); MaterialNode->MaterialExpression = NewPinBase; NewPinBase->SubgraphExpression = NewSubgraphExpression; NewPinBase->ReroutePins.Empty(); for (FCompositeReroute& Reroute : OldPinBase->ReroutePins) { UMaterialExpressionReroute* DupReroute = DuplicateExpression(ToRawPtr(Reroute.Expression)); DupReroute->SubgraphExpression = NewSubgraphExpression; NewPinBase->ReroutePins.Add({ Reroute.Name, decltype(FCompositeReroute::Expression)(DupReroute) }); } UMaterialExpressionComposite* SubGraphComposite = CastChecked(NewSubgraphExpression); if (NewPinBase->PinDirection == EGPD_Output) { SubGraphComposite->InputExpressions = NewPinBase; } else { SubGraphComposite->OutputExpressions = NewPinBase; } } else { MaterialNode->MaterialExpression = DuplicateExpression(ToRawPtr(MaterialNode->MaterialExpression)); MaterialNode->MaterialExpression->SubgraphExpression = NewSubgraphExpression; } // GraphNode is transient so it won't be duplicated. MaterialNode->MaterialExpression->GraphNode = MaterialNode; PostPasteMaterialExpression(MaterialNode->MaterialExpression); } else if (UMaterialGraphNode_Comment* Comment = Cast(MaterialNode)) { Comment->Modify(); Comment->Rename(/*NewName=*/ NULL, /*NewOuter=*/ CopyGraph); Comment->MaterialExpressionComment = DuplicateExpression(ToRawPtr(Comment->MaterialExpressionComment)); Comment->MaterialExpressionComment->SubgraphExpression = NewSubgraphExpression; // GraphNode is transient so it won't be duplicated. Comment->MaterialExpressionComment->GraphNode = Comment; } } } 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->GetExpressionCollection().RemoveExpression(MaterialExpression); Material->RemoveExpressionParameter(MaterialExpression); // Make sure the deleted expression is caught by gc MaterialExpression->MarkAsGarbage(); } Material->MaterialGraph->LinkMaterialExpressionsFromGraph(); } // ScopedTransaction if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->ClearSelectionSet(); FocusedGraphEd->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 = 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 = 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::UpdateDetailView() { GetDetailView()->InvalidateCachedState(); } void FMaterialEditor::OnSelectedNodesChanged(const TSet& NewSelection) { TArray SelectedObjects; UObject* EditObject = Material; if (MaterialFunction) { EditObject = MaterialFunction; } bSelectRegularNode = false; if( NewSelection.Num() == 0 ) { SelectedObjects.Add(EditObject); } else { for(TSet::TConstIterator SetIt(NewSelection);SetIt;++SetIt) { if (UMaterialGraphNode* GraphNode = Cast(*SetIt)) { bSelectRegularNode = true; SelectedObjects.Add(GraphNode->MaterialExpression); } else if (UMaterialGraphNode_Comment* CommentNode = Cast(*SetIt)) { SelectedObjects.Add(CommentNode->MaterialExpressionComment); } else { SelectedObjects.Add(EditObject); } } } GetDetailView()->SetObjects( SelectedObjects, true ); FocusDetailsPanel(); if (bHideUnrelatedNodes && !bLockNodeFadeState) { if (TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin()) { FocusedGraphEd->ResetAllNodesUnrelatedStates(); } if (bSelectRegularNode) { HideUnrelatedNodes(); } } } void FMaterialEditor::OnNodeDoubleClicked(class UEdGraphNode* Node) { UMaterialGraphNode* GraphNode = Cast(Node); if (Node && Node->CanJumpToDefinition()) { Node->JumpToDefinition(); } else if (GraphNode && GraphNode->MaterialExpression) { TOptional InitialColor; bool bCanEditAlpha = true; if(UMaterialExpressionConstant3Vector* Constant3Expression = Cast(GraphNode->MaterialExpression)) { InitialColor = Constant3Expression->Constant; bCanEditAlpha = false; } else if(UMaterialExpressionConstant4Vector* Constant4Expression = Cast(GraphNode->MaterialExpression)) { InitialColor = Constant4Expression->Constant; } else if (UMaterialExpressionFunctionInput* InputExpression = Cast(GraphNode->MaterialExpression)) { InitialColor = InputExpression->PreviewValue; } else if (UMaterialExpressionVectorParameter* VectorExpression = Cast(GraphNode->MaterialExpression)) { InitialColor = VectorExpression->DefaultValue; } if (InitialColor.IsSet()) { TWeakObjectPtr ColorPickerObject = GraphNode->MaterialExpression; // Open a color picker FColorPickerArgs PickerArgs = FColorPickerArgs(InitialColor.GetValue(), FOnLinearColorValueChanged::CreateSP(this, &FMaterialEditor::OnColorPickerCommitted, ColorPickerObject)); PickerArgs.ParentWidget = FocusedGraphEdPtr.Pin(); PickerArgs.bUseAlpha = bCanEditAlpha; PickerArgs.bOnlyRefreshOnOk = false; PickerArgs.bOnlyRefreshOnMouseUp = true; PickerArgs.bExpandAdvancedSection = true; PickerArgs.DisplayGamma = TAttribute::Create( TAttribute::FGetter::CreateUObject(GEngine, &UEngine::GetDisplayGamma) ); 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) { GEditor->GetEditorSubsystem()->OpenEditorForAsset(ObjectToEdit); } // Double click actions for named reroute nodes if (GraphNode->MaterialExpression->IsA()) { OnSelectNamedRerouteUsages(); } else if (GraphNode->MaterialExpression->IsA()) { OnSelectNamedRerouteDeclaration(); } } } void FMaterialEditor::OnRenameNode() { TSharedPtr FocusedGraphEd = FocusedGraphEdPtr.Pin(); if (!FocusedGraphEd) { return; } const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { UEdGraphNode* SelectedNode = Cast(*NodeIt); if (SelectedNode != nullptr && SelectedNode->GetCanRenameNode()) { bool ToRename = true; FocusedGraphEd->IsNodeTitleVisible(SelectedNode, ToRename); break; } } } bool FMaterialEditor::CanRenameNodes() const { const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if ((Node != nullptr) && Node->GetCanRenameNode()) { return true; } } return false; } void FMaterialEditor::OnNodeTitleCommitted(const FText& NewText, ETextCommit::Type CommitInfo, UEdGraphNode* NodeBeingChanged) { if (NodeBeingChanged) { FString NewName = NewText.ToString().TrimStartAndEnd(); if (NewName.IsEmpty()) { // Ignore empty names return; } else if (NewName.Len() >= NAME_SIZE) { UE_LOG(LogMaterialEditor, Warning, TEXT("New material graph node name '%s...' exceeds maximum length of %d and thus was truncated."), *NewName.Left(8), NAME_SIZE - 1); NewName = NewName.Left(NAME_SIZE - 1); } const FScopedTransaction Transaction( LOCTEXT( "RenameNode", "Rename Node" ) ); NodeBeingChanged->Modify(); NodeBeingChanged->OnRenameNode(NewName); UpdateGenerator(); MaterialCustomPrimitiveDataWidget->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; if (FMaterialEditorSpawnNodeCommands::IsRegistered()) { 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->GetExpressionCollection().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(); } // Also request to update the Substrate slab. StrataWidget->UpdateFromMaterial(); } void FMaterialEditor::NotifyExternalMaterialChange() { MaterialStatsManager->SignalMaterialChanged(); } void FMaterialEditor::FocusDetailsPanel() { if (SpawnedDetailsTab.IsValid() && !SpawnedDetailsTab.Pin()->IsForeground()) { SpawnedDetailsTab.Pin()->DrawAttention(); } } void FMaterialEditor::RebuildInheritanceList() { if (!MaterialFunction) { MaterialChildList.Empty(); UMaterialEditingLibrary::GetChildInstances(OriginalMaterial, MaterialChildList); } } #undef LOCTEXT_NAMESPACE