Files
UnrealEngine/Engine/Plugins/Interchange/Editor/Source/Pipelines/Private/SInterchangePipelineConfigurationDialog.cpp
2025-05-18 13:04:45 +08:00

1772 lines
57 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SInterchangePipelineConfigurationDialog.h"
#include "InterchangeEditorPipelineDetails.h"
#include "DetailsViewArgs.h"
#include "Dialog/SCustomDialog.h"
#include "Editor.h"
#include "Editor/Transactor.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/Views/TableViewMetadata.h"
#include "GameFramework/Actor.h"
#include "IDetailsView.h"
#include "IDocumentation.h"
#include "InterchangeCardsPipeline.h"
#include "InterchangeEditorPipelineStyle.h"
#include "InterchangeManager.h"
#include "InterchangePipelineConfigurationBase.h"
#include "InterchangeProjectSettings.h"
#include "InterchangeTranslatorBase.h"
#include "Interfaces/IMainFrameModule.h"
#include "Layout/Visibility.h"
#include "Misc/App.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/ConfigContext.h"
#include "Misc/ScopedSlowTask.h"
#include "Nodes/InterchangeBaseNodeContainer.h"
#include "Nodes/InterchangeSourceNode.h"
#include "PropertyEditorModule.h"
#include "SInterchangeGraphInspectorWindow.h"
#include "SInterchangeTranslatorSettingsDialog.h"
#include "SPrimaryButton.h"
#include "Styling/SlateIconFinder.h"
#include "Styling/StyleColors.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Layout/SSeparator.h"
#include "Widgets/Input/STextComboBox.h"
#include "Widgets/Layout/SUniformGridPanel.h"
#include "Widgets/Input/SEditableTextBox.h"
#define LOCTEXT_NAMESPACE "InterchangePipelineConfiguration"
static bool GInterchangeDefaultShowEssentialsView = false;
static FAutoConsoleVariableRef CCvarInterchangeDefaultShowEssentialsView(
TEXT("Interchange.FeatureFlags.Import.DefaultShowEssentialsView"),
GInterchangeDefaultShowEssentialsView,
TEXT("Whether the import dialog starts by default in essential pipeline properties layout."),
ECVF_Default);
static bool GInterchangeDefaultShowSettings = false;
static FAutoConsoleVariableRef CCvarInterchangeDefaultShowSettings(
TEXT("Interchange.FeatureFlags.Import.DefaultShowSettingsView"),
GInterchangeDefaultShowSettings,
TEXT("Whether the import dialog shows the settings by default. Settings mode is always shown if GInterchangeDefaultHideCardsView is true."),
ECVF_Default);
static bool GInterchangeDefaultHideCardsView = true;
static FAutoConsoleVariableRef CCvarInterchangeDefaultHideCardsView(
TEXT("Interchange.FeatureFlags.Import.DefaultHideCardsView"),
GInterchangeDefaultHideCardsView,
TEXT("Whether the import dialog should hide the basic cards view."),
ECVF_Default);
static bool GInterchangeShowConflictWarningsOnCardsView = true;
static FAutoConsoleVariableRef CCvarInterchangeShowConflictWarningOnCardsView(
TEXT("Interchange.FeatureFlags.Import.ShowConflictWarningsOnCardsView"),
GInterchangeShowConflictWarningsOnCardsView,
TEXT("Whether the import conflict warnings will be shown on cards view."),
ECVF_Default);
constexpr double AdvancedUIRatio = 1.25;
const FName ReimportStackName = TEXT("ReimportPipeline");
void SInterchangePipelineItem::Construct(
const FArguments& InArgs,
const TSharedRef<STableViewBase>& OwnerTable,
TSharedPtr<FInterchangePipelineItemType> InPipelineElement)
{
LLM_SCOPE_BYNAME(TEXT("Interchange"));
PipelineElement = InPipelineElement;
TObjectPtr<UInterchangePipelineBase> PipelineElementPtr = PipelineElement->Pipeline;
check(PipelineElementPtr.Get());
FText PipelineName = LOCTEXT("InvalidPipelineName", "Invalid Pipeline");
if (PipelineElementPtr.Get())
{
FString PipelineNameString = PipelineElement->DisplayName;
if (!PipelineElement->bShowEssentials)
{
PipelineNameString += FString::Printf(TEXT(" (%s)"), *PipelineElementPtr->GetClass()->GetName());
}
PipelineName = FText::FromString(PipelineNameString);
}
static const FSlateBrush* ConflictBrush = FAppStyle::GetBrush("Icons.Error");
const FText ConflictsComboBoxTooltip = LOCTEXT("ConflictsComboBoxTooltip", "If there is some conflict, simply select one to see more details.");
const FText Conflict_IconTooltip = FText::Format(LOCTEXT("Conflict_IconTooltip", "There are {0} conflicts. See Conflicts section below for details."), PipelineElement->ConflictInfos.Num());
STableRow<TSharedPtr<FInterchangePipelineItemType>>::Construct(
STableRow<TSharedPtr<FInterchangePipelineItemType>>::FArguments()
.Content()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0.0f, 2.0f, 6.0f, 2.0f)
[
SNew(SImage)
.Image(this, &SInterchangePipelineItem::GetImageItemIcon)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(0.0f, 2.0f, 6.0f, 2.0f)
[
SNew(SImage)
.ToolTipText(Conflict_IconTooltip)
.Image(ConflictBrush)
.Visibility_Lambda([this]()->EVisibility
{
return PipelineElement->ConflictInfos.Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed;
})
.ColorAndOpacity(this, &SInterchangePipelineItem::GetTextColor)
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(3.0f, 0.0f)
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.Text(PipelineName)
.ColorAndOpacity(this, &SInterchangePipelineItem::GetTextColor)
]
], OwnerTable);
}
const FSlateBrush* SInterchangePipelineItem::GetImageItemIcon() const
{
const FSlateBrush* TypeIcon = nullptr;
FName IconName = "PipelineConfigurationIcon.Pipeline";
const FSlateIcon SlateIcon = FSlateIconFinder::FindIcon(IconName);
TypeIcon = SlateIcon.GetOptionalIcon();
if (!TypeIcon)
{
TypeIcon = FSlateIconFinder::FindIconBrushForClass(AActor::StaticClass());
}
return TypeIcon;
}
FSlateColor SInterchangePipelineItem::GetTextColor() const
{
if (PipelineElement->ConflictInfos.Num() > 0)
{
return FStyleColors::Warning;
}
return FSlateColor::UseForeground();
}
/************************************************************************/
/* SInterchangePipelineConfigurationDialog Implementation */
/************************************************************************/
namespace UE::Private
{
bool ContainStack(const TArray<FInterchangeStackInfo>& PipelineStacks, const FName StackName)
{
return PipelineStacks.FindByPredicate([StackName](const FInterchangeStackInfo& StackInfo)
{
return (StackInfo.StackName == StackName);
}) != nullptr;
}
}
SInterchangePipelineConfigurationDialog::SInterchangePipelineConfigurationDialog()
{
PipelineConfigurationDetailsView = nullptr;
OwnerWindow = nullptr;
}
SInterchangePipelineConfigurationDialog::~SInterchangePipelineConfigurationDialog()
{
if (TSharedPtr<SWindow> OwnerWindowPinned = OwnerWindow.Pin())
{
OwnerWindowPinned->GetOnWindowClosedEvent().RemoveAll(this);
}
if (PreviewNodeContainer)
{
PreviewNodeContainer->ClearFlags(RF_Standalone | RF_Public);
PreviewNodeContainer->ClearInternalFlags(EInternalObjectFlags::Async);
PreviewNodeContainer = nullptr;
}
}
// Pipelines are renamed with the reimport prefix to avoid conflicts with the duplicates of the original pipelines that end up in the same package.
// As this is the name displayed in the Dialog, conflicts won't matter.
FString SInterchangePipelineConfigurationDialog::GetPipelineDisplayName(const UInterchangePipelineBase* Pipeline)
{
FString PipelineDisplayName = Pipeline->ScriptedGetPipelineDisplayName();
if(PipelineDisplayName.IsEmpty())
{
PipelineDisplayName = Pipeline->GetName();
}
return PipelineDisplayName;
}
void SInterchangePipelineConfigurationDialog::SetEditPipeline(FInterchangePipelineItemType* PipelineItemToEdit)
{
TArray<UObject*> ObjectsToEdit;
ObjectsToEdit.Add(!PipelineItemToEdit ? nullptr : PipelineItemToEdit->Pipeline);
if (PipelineItemToEdit)
{
PipelineItemToEdit->ConflictInfos.Reset();
PipelineItemToEdit->ConflictInfos = PipelineItemToEdit->Pipeline->GetConflictInfos(PipelineItemToEdit->ReimportObject, PipelineItemToEdit->Container, PipelineItemToEdit->SourceData);
FInterchangePipelineBaseDetailsCustomization::SetConflictsInfo(PipelineItemToEdit->ConflictInfos);
//Acquire ExtraInformation from SourceNode and pass it to FInterchangePipelineBaseDetailsCustomization:
TMap<FString, FString> ExtraInformation;
const UInterchangeSourceNode* SourceNode = UInterchangeSourceNode::GetUniqueInstance(PipelineItemToEdit->Container);
if (SourceNode)
{
SourceNode->GetExtraInformation(ExtraInformation);
}
FInterchangePipelineBaseDetailsCustomization::SetExtraInformation(ExtraInformation);
}
PipelineConfigurationDetailsView->SetObjects(ObjectsToEdit);
}
FReply SInterchangePipelineConfigurationDialog::OnEditTranslatorSettings()
{
if (!TranslatorSettings || !Translator.IsValid())
{
return FReply::Handled();
}
TSharedRef<SInterchangeTranslatorSettingsDialog> OptionsDialog =
SNew(SInterchangeTranslatorSettingsDialog)
.WindowArguments(SWindow::FArguments()
.IsTopmostWindow(false)
.MinWidth(500)
.MinHeight(400)
.ClientSize(FVector2f{500, 400})
.SizingRule(ESizingRule::UserSized))
.TranslatorSettings(TranslatorSettings);
OptionsDialog->GetTranslatorSettingsDialogClosed().BindLambda([this](bool bUserResponse, bool bTranslatorSettingsChanged)
{
if (!bTranslatorSettingsChanged)
{
return;
}
if (UClass* TranslatorSettingsClass = TranslatorSettings->GetClass())
{
//Save the config locally before the translation.
TranslatorSettings->SaveSettings();
//Need to Translate the source data
FScopedSlowTask Progress(2.f, NSLOCTEXT("SInterchangePipelineConfigurationDialog", "TranslatingSourceFile...", "Translating source file..."));
Progress.MakeDialog();
Progress.EnterProgressFrame(1.f);
//Reset the container
BaseNodeContainer->Reset();
Translator->Translate(*BaseNodeContainer.Get());
//Refresh the dialog
UpdateStack(CurrentStackName);
Progress.EnterProgressFrame(1.f);
}
});
OptionsDialog->ShowModal();
return FReply::Handled();
}
void SInterchangePipelineConfigurationDialog::GatherConflictAndExtraInfo(TArray<FInterchangeConflictInfo>& ConflictInfo, TMap<FString, FString>& ExtraInfo)
{
if (CurrentSelectedPipelineItem.IsValid())
{
TSharedPtr<FInterchangePipelineItemType> PipelineItem = CurrentSelectedPipelineItem.Pin();
ConflictInfo = PipelineItem->Pipeline->GetConflictInfos(PipelineItem->ReimportObject, PipelineItem->Container, PipelineItem->SourceData);
const UInterchangeSourceNode* SourceNode = UInterchangeSourceNode::GetUniqueInstance(PipelineItem->Container);
if (SourceNode)
{
SourceNode->GetExtraInformation(ExtraInfo);
}
}
}
TSharedRef<SBox> SInterchangePipelineConfigurationDialog::SpawnPipelineConfiguration()
{
const ISlateStyle* InterchangeEditorPipelineStyle = FSlateStyleRegistry::FindSlateStyle("InterchangeEditorPipelineStyle");
AvailableStacks.Reset();
TSharedPtr<FString> SelectedStack;
if (bReimport)
{
CurrentStackName = ReimportStackName;
}
else
{
CurrentStackName = FInterchangeProjectSettingsUtils::GetDefaultPipelineStackName(bSceneImport, *SourceData);
}
//In case we do not have a valid stack name use the first stack
FName FirstStackName = PipelineStacks.Num() > 0 ? PipelineStacks[0].StackName : CurrentStackName;
if (bTestConfigDialog || !UE::Private::ContainStack(PipelineStacks, CurrentStackName))
{
CurrentStackName = FirstStackName;
}
for(FInterchangeStackInfo& Stack : PipelineStacks)
{
TSharedPtr<FString> StackNamePtr = MakeShared<FString>(Stack.StackName.ToString());
if (CurrentStackName == Stack.StackName)
{
for (const TObjectPtr<UInterchangePipelineBase>& DefaultPipeline : Stack.Pipelines)
{
check(DefaultPipeline);
if (UInterchangePipelineBase* GeneratedPipeline = UE::Interchange::GeneratePipelineInstance(DefaultPipeline))
{
GeneratedPipeline->TransferAdjustSettings(DefaultPipeline);
if (GeneratedPipeline->IsFromReimportOrOverride())
{
//We save the pipeline settings to allow Reset to Default to work
GeneratedPipeline->SaveSettings(Stack.StackName);
}
else
{
constexpr bool bResetPreDialogTrue = true;
//Load the settings for this pipeline
GeneratedPipeline->LoadSettings(Stack.StackName, bResetPreDialogTrue);
GeneratedPipeline->PreDialogCleanup(Stack.StackName);
}
GeneratedPipeline->SetShowEssentialsMode(bShowEssentials);
if (bFilterOptions && BaseNodeContainer.IsValid())
{
GeneratedPipeline->FilterPropertiesFromTranslatedData(BaseNodeContainer.Get());
}
PipelineListViewItems.Add(MakeShareable(new FInterchangePipelineItemType{ GetPipelineDisplayName(DefaultPipeline), GeneratedPipeline, ReimportObject.Get(), BaseNodeContainer.Get(), SourceData.Get(), bShowEssentials }));
}
}
SelectedStack = StackNamePtr;
}
AvailableStacks.Add(StackNamePtr);
}
FText PipelineListTooltip = LOCTEXT("PipelineListTooltip", "Select a pipeline you want to edit properties for. The pipeline properties will be recorded and changes will be available in subsequent use of that pipeline");
PipelinesListView = SNew(SPipelineListViewType)
.SelectionMode(ESelectionMode::Single)
.ListItemsSource(&PipelineListViewItems)
.OnGenerateRow(this, &SInterchangePipelineConfigurationDialog::MakePipelineListRowWidget)
.OnSelectionChanged(this, &SInterchangePipelineConfigurationDialog::OnPipelineSelectionChanged)
.ClearSelectionOnClick(false)
.ToolTipText(PipelineListTooltip);
TSharedPtr<STextComboBox> TextComboBoxPtr;
//Only use a combo box if there is more then one stack
if (AvailableStacks.Num() > 0)
{
FText StackComboBoxTooltip = LOCTEXT("StackComboBoxTooltip", "Selected pipeline stack preset will be used for the current import. See the Interchange project settings to modify the pipeline stacks preset list.");
TextComboBoxPtr = SNew(STextComboBox)
.OptionsSource(&AvailableStacks)
.OnSelectionChanged(this, &SInterchangePipelineConfigurationDialog::OnStackSelectionChanged)
.ContentPadding(FMargin(0.0f, 2.0f))
.ToolTipText(StackComboBoxTooltip);
if (SelectedStack.IsValid())
{
TextComboBoxPtr->SetSelectedItem(SelectedStack);
}
}
FText CurrentStackText = LOCTEXT("CurrentStackText", "Stacks Preset");
TSharedPtr<SWidget> StackTextComboBox;
if (!TextComboBoxPtr.IsValid())
{
CurrentStackText = LOCTEXT("CurrentStackTextNoComboBox", "There is no pipeline stack preset available");
StackTextComboBox = SNew(SBox)
[
SNew(STextBlock)
.Text(CurrentStackText)
];
}
else
{
StackTextComboBox = SNew(SBox)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 8.0f, 0.0f)
.AutoWidth()
[
SNew(SBox)
[
SNew(STextBlock)
.Text(CurrentStackText)
]
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
TextComboBoxPtr.ToSharedRef()
]
];
}
TSharedPtr<SWidget> StackAndGroupWidget;
//Groups
FText GroupUsedText = LOCTEXT("GroupUsedText", "Group Used:");
FInterchangeGroup::EUsedGroupStatus UsedGroupStatus;
const FInterchangeGroup& UsedInterchangeGroup = FInterchangeProjectSettingsUtils::GetUsedGroup(UsedGroupStatus);
switch (UsedGroupStatus)
{
case FInterchangeGroup::NotSet:
StackAndGroupWidget = SNew(SBox)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
StackTextComboBox.ToSharedRef()
]
];
break;
case FInterchangeGroup::SetAndValid:
{
FText GroupComboBoxTooltip = LOCTEXT("GroupComboBoxTooltip", "Group usage can be set in Editor Preferences > Interchange > Groups.");
StackAndGroupWidget = SNew(SBox)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
StackTextComboBox.ToSharedRef()
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SBox)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(16.0f, 0.0f, 4.0f, 0.0f)
.AutoWidth()
[
SNew(SBox)
[
SNew(STextBlock)
.Text(GroupUsedText)
]
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SEditableTextBox)
.Text(FText::FromName(UsedInterchangeGroup.DisplayName))
.IsEnabled(false)
.ToolTipText(GroupComboBoxTooltip)
]
]
]
];
}
break;
case FInterchangeGroup::SetAndInvalid:
{
//invalid Group usage:
FText InvalidGroupText = LOCTEXT("InvalidGroupText", "Invalid Group setup for usage!");
FText InvalidGroupTooltip = LOCTEXT("InvalidGroupTooltip", "Please review Group usage in Editor Preferences > Interchange > Groups.");
StackAndGroupWidget = SNew(SBox)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
StackTextComboBox.ToSharedRef()
]
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.AutoWidth()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.VAlign(VAlign_Center)
.Padding(16.0f, 0.0f, 0.0f, 0.0f)
.AutoWidth()
[
SNew(SBox)
[
SNew(STextBlock)
.Text(InvalidGroupText)
.ToolTipText(InvalidGroupTooltip)
]
]
]
];
}
break;
default:
break;
}
TSharedPtr<SBox> InspectorBox;
FText FilterPipelineTooltip = LOCTEXT("SInterchangePipelineConfigurationDialog_FilterPipelineOptions_tooltip", "Filter the pipeline options using the source content data.");
FText EssentialPipelineTooltip = LOCTEXT("SInterchangePipelineConfigurationDialog_ShowEssentialsOptions_tooltip", "Display only essentials pipeline properties.");
TSharedRef<SBox> PipelineConfigurationPanelBox = SNew(SBox)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.Padding(0.0f, 8.0f)
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
[
StackAndGroupWidget.ToSharedRef()
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNullWidget::NullWidget
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f, 0.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(4.f, 0.f)
[
SNew(STextBlock)
.Text(LOCTEXT("SInterchangePipelineConfigurationDialog_ShowEssentialsOptions", "Essentials"))
.ToolTipText(EssentialPipelineTooltip)
]
+ SHorizontalBox::Slot()
.Padding(4.f, 0.f)
[
SNew(SCheckBox)
.IsChecked(this, &SInterchangePipelineConfigurationDialog::IsShowEssentialsEnabled)
.OnCheckStateChanged(this, &SInterchangePipelineConfigurationDialog::OnShowEssentialsChanged)
.ToolTipText(EssentialPipelineTooltip)
]
]
+SHorizontalBox::Slot()
.AutoWidth()
.Padding(4.0f, 0.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(4.f, 0.f)
[
SNew(STextBlock)
.Text(LOCTEXT("SInterchangePipelineConfigurationDialog_FilterPipelineOptions", "Filter on Contents"))
.ToolTipText(FilterPipelineTooltip)
]
+ SHorizontalBox::Slot()
.Padding(4.f, 0.f)
[
SNew(SCheckBox)
.IsChecked(this, &SInterchangePipelineConfigurationDialog::IsFilteringOptions)
.OnCheckStateChanged(this, &SInterchangePipelineConfigurationDialog::OnFilterOptionsChanged)
.ToolTipText(FilterPipelineTooltip)
]
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(4.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SButton)
.IsEnabled_Lambda([this]()
{
return PipelinesListView->GetNumItemsSelected() == 1;
})
.Text(LOCTEXT("SInterchangePipelineConfigurationDialog_ResetToPipelineAsset", "Use Pipeline Defaults"))
.ToolTipText_Lambda([bReimportClosure = bReimport]()
{
if (bReimportClosure)
{
return LOCTEXT("SInterchangePipelineConfigurationDialog_ResetToPipelineAsset_TooltipReimport", "Reset the selected pipeline to is values used the last time this asset was imported.");
}
else
{
return LOCTEXT("SInterchangePipelineConfigurationDialog_ResetToPipelineAsset_Tooltip", "Reset the selected pipeline to is default values.");
}
})
.OnClicked(this, &SInterchangePipelineConfigurationDialog::OnResetToDefault)
]
]
+ SVerticalBox::Slot()
.Padding(0.0f, 8.0f)
.AutoHeight()
[
SNew(SBox)
.MinDesiredHeight(50)
.MaxDesiredHeight(140)
[
PipelinesListView.ToSharedRef()
]
]
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SAssignNew(InspectorBox, SBox)
]
];
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.bAllowSearch = true;
DetailsViewArgs.bShowPropertyMatrixButton = false;
DetailsViewArgs.bShowSectionSelector = true;
DetailsViewArgs.bAllowMultipleTopLevelObjects = true;
DetailsViewArgs.bShowModifiedPropertiesOption = false;
DetailsViewArgs.bShowKeyablePropertiesOption = false;
DetailsViewArgs.bShowAnimatedPropertiesOption = false;
DetailsViewArgs.bShowHiddenPropertiesWhilePlayingOption = false;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
PipelineConfigurationDetailsView = PropertyEditorModule.CreateDetailView(DetailsViewArgs);
InspectorBox->SetContent(PipelineConfigurationDetailsView->AsShared());
SetEditPipeline(nullptr);
PipelineConfigurationDetailsView->GetIsPropertyVisibleDelegate().BindLambda([this](const FPropertyAndParent& PropertyAndParent)
{
return IsPropertyVisible(PropertyAndParent);
});
PipelineConfigurationDetailsView->OnFinishedChangingProperties().AddLambda([this](const FPropertyChangedEvent& PropertyChangedEvent)
{
if (CurrentSelectedPipeline && CurrentSelectedPipeline->IsPropertyChangeNeedRefresh(PropertyChangedEvent))
{
//Refresh the pipeline
UpdateStack(CurrentStackName);
}
});
return PipelineConfigurationPanelBox;
}
TSharedRef<SBox> SInterchangePipelineConfigurationDialog::SpawnCardsConfiguration()
{
const FSlateBrush* AdvanceSettingsIcon = FSlateIconFinder::FindIcon("PipelineConfigurationIcon.SidePanelRight").GetOptionalIcon();
CreateCardsViewList();
TSharedRef<SWidget> BodyWidget = CardViewList.IsValid() ? CardViewList.ToSharedRef() : SNullWidget::NullWidget;
TSharedRef<SBox> CardsConfigurationPanelBox = SNew(SBox)
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.HAlign(HAlign_Fill)
.Padding(0.0f, 8.0f)
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.HAlign(HAlign_Left)
[
SNew(STextBlock)
.Text(LOCTEXT("SInterchangePipelineConfigurationDialog_AssetFoundText", "Assets Found in Source:"))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SCheckBox)
.Padding(FMargin(8.0, 4.0, 0.0, 4.0))
.Style(&FAppStyle::Get().GetWidgetStyle<FCheckBoxStyle>("ToggleButtonCheckBox"))
.Type(ESlateCheckBoxType::ToggleButton)
.IsChecked_Lambda([this]() { return bShowSettings ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; })
.OnCheckStateChanged_Lambda([this](ECheckBoxState CheckState)
{
bShowSettings = CheckState == ECheckBoxState::Checked;
TSharedPtr<SWindow> DialogWindow = OwnerWindow.Pin();
FVector2D ClientSize = DialogWindow->GetClientSizeInScreen();
FWindowSizeLimits SizeLimits = DialogWindow->GetSizeLimits();
double MinimumSizeX = 0.0;
if (bShowCards)
{
//Add show cards width
MinimumSizeX += OriginalMinWindowSize;
}
if (bShowSettings)
{
//Add settings width
MinimumSizeX += (OriginalMinWindowSize * AdvancedUIRatio);
}
//Resize the client with a updated minimum size width
SizeLimits.SetMinWidth(static_cast<float>(MinimumSizeX));
DialogWindow->SetSizeLimits(SizeLimits);
DialogWindow->Resize(ClientSize);
UpdateStack(CurrentStackName);
GConfig->SetBool(TEXT("InterchangeImportDialogOptions"), TEXT("ShowSettings"), bShowSettings, GEditorPerProjectIni);
})
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 8.0f, 0.0f)
[
SNew(SImage)
.Image(AdvanceSettingsIcon)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(0.0f, 0.0f, 8.0f, 0.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("SInterchangePipelineConfigurationDialog_AdvanceSettingsButtonText", "Advanced Settings"))
]
]
]
]
+ SVerticalBox::Slot()
.Padding(0.0f, 8.0f)
.FillHeight(1.0f)
[
SNew(SScrollBox)
+ SScrollBox::Slot()
[
BodyWidget
]
]
];
return CardsConfigurationPanelBox;
}
void SInterchangePipelineConfigurationDialog::Construct(const FArguments& InArgs)
{
LLM_SCOPE_BYNAME(TEXT("Interchange"));
if (GEditor != nullptr && GEditor->Trans != nullptr)
{
GEditor->Trans->SetUndoBarrier();
}
//Make sure there is a valid default value
OwnerWindow = InArgs._OwnerWindow;
SourceData = InArgs._SourceData;
bSceneImport = InArgs._bSceneImport;
bReimport = InArgs._bReimport;
bTestConfigDialog = InArgs._bTestConfigDialog;
PipelineStacks = InArgs._PipelineStacks;
OutPipelines = InArgs._OutPipelines;
BaseNodeContainer = InArgs._BaseNodeContainer;
ReimportObject = InArgs._ReimportObject;
SourceData = InArgs._SourceData;
Translator = InArgs._Translator;
if (Translator.IsValid())
{
TranslatorSettings = Translator->GetSettings();
}
if (ReimportObject.IsValid())
{
ensure(bReimport);
}
FText ReuseSettingsTooltipText = LOCTEXT("InspectorGraphWindow_ReuseSettingsTooltipText", "When importing multiple files this checkbox allow users to use the same settings for source of the same extension.");
const bool bTranslatorThreadSafe = Translator.IsValid() ? Translator->IsThreadSafe() : false;
if (!bTranslatorThreadSafe)
{
FString Extension = SourceData.IsValid() ? FPaths::GetExtension(SourceData->GetFilename()) : TEXT("N/A");
ReuseSettingsTooltipText = FText::Format(LOCTEXT("InspectorGraphWindow_ReuseSettingsNotThreadSafeTooltipText", "{0} translator is not thread safe and must use the same settings for subsequent files"), FText::FromString(Extension));
}
check(OutPipelines);
check(OwnerWindow.IsValid());
TSharedPtr<SWindow> OwnerWindowPinned = OwnerWindow.Pin();
if (OwnerWindowPinned.IsValid())
{
OwnerWindowPinned->GetOnWindowClosedEvent().AddRaw(this, &SInterchangePipelineConfigurationDialog::OnWindowClosed);
OriginalMinWindowSize = OwnerWindowPinned->GetSizeLimits().GetMinWidth().Get(0.0);
if (OriginalMinWindowSize < 1.0)
{
OriginalMinWindowSize = 550.0;
}
DeltaClientWindowSize = (OwnerWindowPinned->GetSizeInScreen() - OwnerWindowPinned->GetClientSizeInScreen()).X;
}
//Get the default layout when the user open the import dialog for the first time.
bShowEssentials = GInterchangeDefaultShowEssentialsView;
bShowCards = !GInterchangeDefaultHideCardsView && !bSceneImport;
bShowSettings = GInterchangeDefaultShowSettings || !bShowCards;
if (bReimport)
{
bFilterOptions = false;
}
if(GConfig->DoesSectionExist(TEXT("InterchangeImportDialogOptions"), GEditorPerProjectIni))
{
if (!bReimport)
{
GConfig->GetBool(TEXT("InterchangeImportDialogOptions"), TEXT("FilterOptions"), bFilterOptions, GEditorPerProjectIni);
}
GConfig->GetBool(TEXT("InterchangeImportDialogOptions"), TEXT("ShowEssentials"), bShowEssentials, GEditorPerProjectIni);
GConfig->GetBool(TEXT("InterchangeImportDialogOptions"), TEXT("ShowSettings"), bShowSettings, GEditorPerProjectIni);
//Make sure settings are shown if we hide cards
if(!bShowCards)
{
bShowSettings = true;
}
GConfig->GetDouble(TEXT("InterchangeImportDialogOptions"), TEXT("SplitAdvancedRatio"), SplitAdvancedRatio, GEditorPerProjectIni);
}
//Make sure the windows is width enough to show all the ui part (cards and settings)
if (OwnerWindowPinned.IsValid())
{
FVector2D WidowsClientSize = OwnerWindowPinned->GetClientSizeInScreen();
FWindowSizeLimits SizeLimits = OwnerWindowPinned->GetSizeLimits();
double MinimumSizeX = 0.0;
if (bShowCards)
{
//Add show cards width
MinimumSizeX += OriginalMinWindowSize;
}
if (bShowSettings)
{
//Add settings width
MinimumSizeX += (OriginalMinWindowSize * AdvancedUIRatio);
}
SizeLimits.SetMinWidth(static_cast<float>(MinimumSizeX));
OwnerWindowPinned->SetSizeLimits(SizeLimits);
//Resize the window to respect the limits
OwnerWindowPinned->Resize(WidowsClientSize);
}
//SpawnPipelineConfiguration must always be call because it create the pipeline list from the project settings
TSharedRef<SBox> MainBodyAdvanced = SpawnPipelineConfiguration();
UpdatePipelineSupportedAssetClasses();
TSharedRef<SBox> MainBodyCardsConfiguration = SpawnCardsConfiguration();
const FSlateBrush* TranslatorSettingsIcon = FSlateIconFinder::FindIcon("PipelineConfigurationIcon.TranslatorSettings").GetOptionalIcon();
const ISlateStyle* InterchangeEditorPipelineStyle = FSlateStyleRegistry::FindSlateStyle("InterchangeEditorPipelineStyle");
const FSlateBrush* ImportSourceBorderBrush = nullptr;
if (InterchangeEditorPipelineStyle)
{
ImportSourceBorderBrush = InterchangeEditorPipelineStyle->GetBrush("ImportSource.Dropdown.Border");
}
this->ChildSlot
[
SNew(SBorder)
.Padding(FMargin(16.0f, 16.0f))
.BorderImage(FAppStyle::GetBrush("ToolPanel.DarkGroupBorder"))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.HAlign(HAlign_Fill)
.Padding(0.0f, 0.0f, 0.0f, 8.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(8.0f, 0.0f, 8.0f, 0.0f)
[
SNew(STextBlock)
.Text(LOCTEXT("SInterchangePipelineConfigurationDialog_SourceLabel", "Import Source"))
]
+SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
[
SNew(SBorder)
.BorderImage(ImportSourceBorderBrush)
.Padding(0.0f, 4.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.VAlign(VAlign_Center)
.Padding(8.0f, 0.0f, 0.0f, 0.0f)
[
SNew(STextBlock)
.Text(this, &SInterchangePipelineConfigurationDialog::GetSourceDescription)
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SSeparator)
.Orientation(EOrientation::Orient_Vertical)
.Thickness(1.0f)
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SButton)
.ButtonStyle(&FAppStyle::Get().GetWidgetStyle<FButtonStyle>("NoBorder"))
.ContentPadding(FMargin(0.0f))
.Visibility_Lambda([this]()
{
return !TranslatorSettings ? EVisibility::Collapsed : EVisibility::Visible;
})
.ToolTipText(LOCTEXT("SInterchangePipelineConfigurationDialog_TranslatorSettings_Tooltip", "Edit translator project settings."))
.OnClicked(this, &SInterchangePipelineConfigurationDialog::OnEditTranslatorSettings)
[
SNew(SImage)
.Image(TranslatorSettingsIcon)
]
]
]
]
]
+ SVerticalBox::Slot()
.FillHeight(1.0f)
[
SAssignNew(CardsAndAdvancedSplitter, SSplitter)
.OnSplitterFinishedResizing_Lambda([this]()
{
SplitAdvancedRatio = (1.0 - static_cast<double>(CardsAndAdvancedSplitter->SlotAt(0).GetSizeValue()));
GConfig->SetDouble(TEXT("InterchangeImportDialogOptions"), TEXT("SplitAdvancedRatio"), SplitAdvancedRatio, GEditorPerProjectIni);
})
+ SSplitter::Slot()
.MinSize(static_cast<float>(OriginalMinWindowSize - DeltaClientWindowSize))
.Value(1.0f - static_cast<float>(SplitAdvancedRatio))
[
SNew(SBox)
.Visibility_Lambda([this]()
{
return bShowCards ? EVisibility::Visible : EVisibility::Collapsed;
})
.Padding_Lambda([this]()
{
return (bShowSettings && bShowCards) ? FMargin(0.0f, 0.0f, 8.0f, 0.0f) : FMargin(0.0f, 0.0f, 0.0f, 0.0f);
})
[
MainBodyCardsConfiguration
]
]
+ SSplitter::Slot()
.MinSize(static_cast<float>((OriginalMinWindowSize * AdvancedUIRatio) - DeltaClientWindowSize))
.Value(static_cast<float>(SplitAdvancedRatio))
[
SNew(SBox)
.Visibility_Lambda([this]()
{
return bShowSettings ? EVisibility::Visible : EVisibility::Collapsed;
})
.Padding_Lambda([this]()
{
return bShowCards ? FMargin(8.0f, 0.0f, 0.0f, 0.0f) : FMargin(0.0f, 0.0f, 0.0f, 0.0f);
})
.Clipping(EWidgetClipping::ClipToBounds)
[
MainBodyAdvanced
]
]
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 8.0, 0.0, 0.0)
[
SNew(SSeparator)
.Orientation(EOrientation::Orient_Horizontal)
]
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(0.0f, 8.0f, 0.0f, 0.0f)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.Padding(0.f, 0.f)
.AutoWidth()
[
IDocumentation::Get()->CreateAnchor(FString("interchange-framework-in-unreal-engine"))
]
+ SHorizontalBox::Slot()
.Padding(4.f, 0.f)
.AutoWidth()
[
SNew(SHorizontalBox)
.ToolTipText(LOCTEXT("InspectorGraphWindow_ReuseSettingsToolTip", "When importing multiple files, keep the same import settings for every file or open the settings dialog for each file."))
+ SHorizontalBox::Slot()
.AutoWidth()
.HAlign(HAlign_Right)
.VAlign(VAlign_Center)
.Padding(4.f, 0.f)
[
SNew(STextBlock)
.IsEnabled(bTranslatorThreadSafe)
.Text(LOCTEXT("InspectorGraphWindow_ReuseSettings", "Use the same settings for subsequent files"))
.ToolTipText(ReuseSettingsTooltipText)
]
+ SHorizontalBox::Slot()
.Padding(4.f, 0.f)
[
SAssignNew(UseSameSettingsForAllCheckBox, SCheckBox)
.IsChecked(true)
.IsEnabled_Lambda([this, bTranslatorThreadSafe]()
{
if (!bTranslatorThreadSafe)
{
return false;
}
return IsImportButtonEnabled();
})
]
]
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
[
SNullWidget::NullWidget
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SUniformGridPanel)
.SlotPadding(FMargin(4.f, 0.f))
+SUniformGridPanel::Slot(0,0)
[
SNew(SPrimaryButton)
.Icon(this, &SInterchangePipelineConfigurationDialog::GetImportButtonIcon)
.Text_Lambda([this]()
{
return bTestConfigDialog ? LOCTEXT("InspectorGraphWindow_SaveConfig", "Save Config") : LOCTEXT("InspectorGraphWindow_Import", "Import");
})
.ToolTipText(this, &SInterchangePipelineConfigurationDialog::GetImportButtonTooltip)
.IsEnabled(this, &SInterchangePipelineConfigurationDialog::IsImportButtonEnabled)
.OnClicked(this, &SInterchangePipelineConfigurationDialog::OnCloseDialog, ECloseEventType::PrimaryButton)
]
+SUniformGridPanel::Slot(1,0)
[
SNew(SButton)
.Visibility_Lambda([this]()
{
return bShowSettings ? EVisibility::Visible : EVisibility::Collapsed;
})
.HAlign(HAlign_Center)
.Text(LOCTEXT("InspectorGraphWindow_Preview", "Preview..."))
.IsEnabled_Lambda([this]()
{
return !bTestConfigDialog;
})
.OnClicked(this, &SInterchangePipelineConfigurationDialog::OnPreviewImport)
]
+SUniformGridPanel::Slot(2,0)
[
SNew(SButton)
.HAlign(HAlign_Center)
.Text(LOCTEXT("InspectorGraphWindow_Cancel", "Cancel"))
.OnClicked(this, &SInterchangePipelineConfigurationDialog::OnCloseDialog, ECloseEventType::Cancel)
]
]
]
]
];
//Select the first pipeline
if (PipelineListViewItems.Num() > 0)
{
bool bSelectFirst = true;
FString LastPipelineName;
FString KeyName = CurrentStackName.ToString() + TEXT("_LastSelectedPipeline");
if (GConfig->GetString(TEXT("InterchangeSelectPipeline"), *KeyName, LastPipelineName, GEditorPerProjectIni))
{
for (TSharedPtr<FInterchangePipelineItemType> PipelineItem: PipelineListViewItems)
{
FString PipelineItemName = PipelineItem->Pipeline->GetClass()->GetName();
if (PipelineItemName.Equals(LastPipelineName))
{
PipelinesListView->SetSelection(PipelineItem, ESelectInfo::Direct);
bSelectFirst = false;
break;
}
}
}
if (bSelectFirst)
{
PipelinesListView->SetSelection(PipelineListViewItems[0], ESelectInfo::Direct);
}
if (GInterchangeShowConflictWarningsOnCardsView)
{
RefreshCardsViewList();
}
}
FInterchangePipelineBaseDetailsCustomization::OnGatherConflictAndExtraInfo.BindRaw(this, &SInterchangePipelineConfigurationDialog::GatherConflictAndExtraInfo);
}
bool SInterchangePipelineConfigurationDialog::IsPropertyVisible(const FPropertyAndParent& PropertyAndParent) const
{
if (bReimport)
{
const FName ReimportRestrictKey(TEXT("ReimportRestrict"));
return !(PropertyAndParent.Property.GetBoolMetaData(ReimportRestrictKey));
}
return true;
}
const FSlateBrush* SInterchangePipelineConfigurationDialog::GetImportButtonIcon() const
{
const FSlateBrush* TypeIcon = nullptr;
if (bShowSettings || GInterchangeShowConflictWarningsOnCardsView)
{
for (TSharedPtr<FInterchangePipelineItemType> PipelineItem : PipelineListViewItems)
{
if (PipelineItem.IsValid() && PipelineItem->Pipeline)
{
if (PipelineItem->ConflictInfos.Num() > 0)
{
const FSlateIcon SlateIcon = FSlateIconFinder::FindIcon("Icons.Warning");
return SlateIcon.GetOptionalIcon();
}
}
}
}
return TypeIcon;
}
FText SInterchangePipelineConfigurationDialog::GetSourceDescription() const
{
FText ActionDescription;
if (SourceData.IsValid())
{
ActionDescription = FText::FromString(SourceData->GetFilename());
}
return ActionDescription;
}
FReply SInterchangePipelineConfigurationDialog::OnResetToDefault()
{
FReply Result = FReply::Handled();
TArray<TWeakObjectPtr<UObject>> SelectedPipelines = PipelineConfigurationDetailsView->GetSelectedObjects();
if (CurrentStackName == NAME_None)
{
return Result;
}
FInterchangePipelineItemType* PipelineToEdit = nullptr;
//Multi selection is not allowed
for(TWeakObjectPtr<UObject> WeakObject : SelectedPipelines)
{
//We test the cast because we can have null or other type selected (i.e. translator settings class default object).
if (UInterchangePipelineBase* Pipeline = Cast<UInterchangePipelineBase>(WeakObject.Get()))
{
const UClass* PipelineClass = Pipeline->GetClass();
for (FInterchangeStackInfo& Stack : PipelineStacks)
{
if (Stack.StackName != CurrentStackName)
{
continue;
}
for (const TObjectPtr<UInterchangePipelineBase>& DefaultPipeline : Stack.Pipelines)
{
//We assume the pipelines inside one stack are all different classes, we use the class to know which default asset we need to duplicate
if (DefaultPipeline->GetClass() == PipelineClass)
{
for(int32 PipelineIndex = 0; PipelineIndex < PipelineListViewItems.Num(); ++PipelineIndex)
{
TObjectPtr<UInterchangePipelineBase> PipelineElement = PipelineListViewItems[PipelineIndex]->Pipeline;
if (PipelineElement.Get() == Pipeline)
{
if (UInterchangePipelineBase* GeneratedPipeline = UE::Interchange::GeneratePipelineInstance(DefaultPipeline))
{
GeneratedPipeline->TransferAdjustSettings(DefaultPipeline);
GeneratedPipeline->SetShowEssentialsMode(bShowEssentials);
if(bFilterOptions && BaseNodeContainer.IsValid())
{
GeneratedPipeline->FilterPropertiesFromTranslatedData(BaseNodeContainer.Get());
}
//Switch the pipeline the element point on
PipelineListViewItems[PipelineIndex]->Pipeline = GeneratedPipeline;
PipelineToEdit = PipelineListViewItems[PipelineIndex].Get();
PipelinesListView->SetSelection(PipelineListViewItems[PipelineIndex], ESelectInfo::Direct);
PipelinesListView->RequestListRefresh();
break;
}
}
}
}
}
}
}
}
SetEditPipeline(PipelineToEdit);
//Update the cards
RefreshCardsViewList();
return Result;
}
bool SInterchangePipelineConfigurationDialog::ValidateAllPipelineSettings(TOptional<FText>& OutInvalidReason) const
{
for (TSharedPtr<FInterchangePipelineItemType> PipelineElement : PipelineListViewItems)
{
check(PipelineElement->Pipeline);
if (!PipelineElement->Pipeline->IsSettingsAreValid(OutInvalidReason))
{
return false;
}
}
return true;
}
bool SInterchangePipelineConfigurationDialog::IsImportButtonEnabled() const
{
TOptional<FText> InvalidReason;
return ValidateAllPipelineSettings(InvalidReason);
}
FText SInterchangePipelineConfigurationDialog::GetImportButtonTooltip() const
{
//Pipeline validation
TOptional<FText> InvalidReason;
if (!ValidateAllPipelineSettings(InvalidReason) && InvalidReason.IsSet())
{
return InvalidReason.GetValue();
}
//Pipeline conflicts
for (TSharedPtr<FInterchangePipelineItemType> PipelineItem : PipelineListViewItems)
{
if (PipelineItem.IsValid() && PipelineItem->Pipeline)
{
if (PipelineItem->ConflictInfos.Num() > 0)
{
return LOCTEXT("ImportButtonConflictTooltip", "There is one or more pipeline conflicts, look at any conflict in the pipeline list to have more detail.");
}
}
}
//Default tooltip
return LOCTEXT("ImportButtonDefaultTooltip", "Selected pipeline stack will be used for the current import");
}
void SInterchangePipelineConfigurationDialog::SaveAllPipelineSettings() const
{
for (TSharedPtr<FInterchangePipelineItemType> PipelineElement : PipelineListViewItems)
{
if (PipelineElement->Pipeline)
{
PipelineElement->Pipeline->SaveSettings(CurrentStackName);
}
}
}
void SInterchangePipelineConfigurationDialog::ClosePipelineConfiguration(const ECloseEventType CloseEventType)
{
if (CloseEventType == ECloseEventType::Cancel || CloseEventType == ECloseEventType::WindowClosing)
{
bCanceled = true;
bImportAll = false;
}
else //ECloseEventType::PrimaryButton
{
bCanceled = false;
bImportAll = UseSameSettingsForAllCheckBox->IsChecked();
//Fill the OutPipelines array
for (TSharedPtr<FInterchangePipelineItemType> PipelineElement : PipelineListViewItems)
{
OutPipelines->Add(PipelineElement->Pipeline);
}
if (UInterchangeCardsPipeline* InterchangeCardsPipeline = GenerateTransientCardsPipeline())
{
//Add the cards pipeline if valid
OutPipelines->Add(InterchangeCardsPipeline);
}
}
//Save the settings only if its not a re-import
if (!bReimport)
{
SaveAllPipelineSettings();
}
PipelineConfigurationDetailsView = nullptr;
if (CloseEventType != ECloseEventType::WindowClosing)
{
if (TSharedPtr<SWindow> OwnerWindowPin = OwnerWindow.Pin())
{
OwnerWindowPin->GetOnWindowClosedEvent().RemoveAll(this);
OwnerWindowPin->RequestDestroyWindow();
}
}
OwnerWindow = nullptr;
FInterchangePipelineBaseDetailsCustomization::OnGatherConflictAndExtraInfo.Unbind();
if (GEditor != nullptr && GEditor->Trans != nullptr)
{
GEditor->Trans->RemoveUndoBarrier();
}
}
FReply SInterchangePipelineConfigurationDialog::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if (InKeyEvent.GetKey() == EKeys::Escape)
{
return OnCloseDialog(ECloseEventType::Cancel);
}
return FReply::Unhandled();
}
void SInterchangePipelineConfigurationDialog::UpdateStack(const FName& NewStackName)
{
LLM_SCOPE_BYNAME(TEXT("Interchange"));
const bool bStackSelectionChange = CurrentStackName != NewStackName;
//Save current stack settings, we want the same settings when we will go back to the same stack
//When doing a reimport we do not want to save the setting because the context have special default
//value for some options like: (Import Materials, Import Textures...).
//So when doing a reimport switching stack is like doing a reset to default on all pipelines
if (!bReimport || !bStackSelectionChange)
{
SaveAllPipelineSettings();
}
CurrentStackName = NewStackName;
int32 CurrentPipelineIndex = 0;
if (!bStackSelectionChange)
{
//store the selected pipeline
for (int32 PipelineIndex = 0; PipelineIndex < PipelineListViewItems.Num(); ++PipelineIndex)
{
const TSharedPtr<FInterchangePipelineItemType> PipelineItem = PipelineListViewItems[PipelineIndex];
if (PipelineItem->Pipeline == CurrentSelectedPipeline)
{
CurrentPipelineIndex = PipelineIndex;
break;
}
}
}
//Rebuild the Pipeline list item
PipelineListViewItems.Reset();
for (FInterchangeStackInfo& Stack : PipelineStacks)
{
TSharedPtr<FString> StackNamePtr = MakeShared<FString>(Stack.StackName.ToString());
if (CurrentStackName != Stack.StackName)
{
continue;
}
for (const TObjectPtr<UInterchangePipelineBase>& DefaultPipeline : Stack.Pipelines)
{
check(DefaultPipeline);
if (UInterchangePipelineBase* GeneratedPipeline = UE::Interchange::GeneratePipelineInstance(DefaultPipeline))
{
GeneratedPipeline->TransferAdjustSettings(DefaultPipeline);
if (!GeneratedPipeline->IsFromReimportOrOverride() || !bStackSelectionChange)
{
//Load the settings for this pipeline
GeneratedPipeline->LoadSettings(Stack.StackName, bStackSelectionChange);
if (bStackSelectionChange)
{
//Do not reset pipeline value if we are just refreshing the filtering
GeneratedPipeline->PreDialogCleanup(Stack.StackName);
}
}
GeneratedPipeline->SetShowEssentialsMode(bShowEssentials);
if (bFilterOptions && BaseNodeContainer.IsValid())
{
GeneratedPipeline->FilterPropertiesFromTranslatedData(BaseNodeContainer.Get());
}
PipelineListViewItems.Add(MakeShareable(new FInterchangePipelineItemType{ GetPipelineDisplayName(DefaultPipeline), GeneratedPipeline, ReimportObject.Get(), BaseNodeContainer.Get(), SourceData.Get(), bShowEssentials }));
}
}
}
//Select the first pipeline
if (PipelineListViewItems.Num() > 0)
{
CurrentPipelineIndex = PipelineListViewItems.IsValidIndex(CurrentPipelineIndex) ? CurrentPipelineIndex : 0;
if (bShowSettings)
{
PipelinesListView->SetSelection(PipelineListViewItems[CurrentPipelineIndex], ESelectInfo::Direct);
PipelinesListView->RequestListRefresh();
}
else
{
for (TSharedPtr<FInterchangePipelineItemType>& PiplineListViewItem : PipelineListViewItems)
{
PiplineListViewItem->ConflictInfos.Reset();
PiplineListViewItem->ConflictInfos = PiplineListViewItem->Pipeline->GetConflictInfos(PiplineListViewItem->ReimportObject, PiplineListViewItem->Container, PiplineListViewItem->SourceData);
}
}
}
//Update the cards
RefreshCardsViewList();
}
void SInterchangePipelineConfigurationDialog::OnStackSelectionChanged(TSharedPtr<FString> String, ESelectInfo::Type)
{
if (!String.IsValid())
{
return;
}
FName NewStackName = FName(*String.Get());
if (!UE::Private::ContainStack(PipelineStacks, NewStackName))
{
return;
}
//Nothing change the selection is the same
if (CurrentStackName == NewStackName)
{
return;
}
UpdateStack(NewStackName);
}
TSharedRef<ITableRow> SInterchangePipelineConfigurationDialog::MakePipelineListRowWidget(
TSharedPtr<FInterchangePipelineItemType> InElement,
const TSharedRef<STableViewBase>& OwnerTable)
{
check(InElement->Pipeline);
return SNew(SInterchangePipelineItem, OwnerTable, InElement);
}
void SInterchangePipelineConfigurationDialog::OnPipelineSelectionChanged(TSharedPtr<FInterchangePipelineItemType> InItem, ESelectInfo::Type SelectInfo)
{
CurrentSelectedPipeline = nullptr;
if (InItem)
{
CurrentSelectedPipeline = InItem->Pipeline;
}
CurrentSelectedPipelineItem = InItem;
SetEditPipeline(InItem.Get());
if (CurrentSelectedPipeline)
{
FString CurrentPipelineName = CurrentSelectedPipeline->GetClass()->GetName();
FString KeyName = CurrentStackName.ToString() + TEXT("_LastSelectedPipeline");
GConfig->SetString(TEXT("InterchangeSelectPipeline"), *KeyName, *CurrentPipelineName, GEditorPerProjectIni);
}
}
void SInterchangePipelineConfigurationDialog::UpdatePipelineSupportedAssetClasses()
{
PipelineSupportAssetClasses.Reset();
for (TSharedPtr<FInterchangePipelineItemType> PipelineItem : PipelineListViewItems)
{
TArray<UClass*> PipelineSupportedClasses;
PipelineItem->Pipeline->GetSupportAssetClasses(PipelineSupportedClasses);
for (UClass* AssetClass : PipelineSupportedClasses)
{
if (!PipelineSupportAssetClasses.Contains(AssetClass))
{
PipelineSupportAssetClasses.Add(AssetClass);
}
}
}
}
void SInterchangePipelineConfigurationDialog::UpdateEnableDataPerFactoryNodeClass()
{
if (!ensure(PreviewNodeContainer))
{
return;
}
TArray<UClass*> ValidCardClasses;
PreviewNodeContainer->IterateNodesOfType<UInterchangeFactoryBaseNode>([this, &ValidCardClasses](const FString& NodeUid, UInterchangeFactoryBaseNode* FactoryNode)
{
UClass* NodeObjectClass = FactoryNode->GetObjectClass();
// Don't add factory node class twice.
// Don't add factory node class that don't have a valid object class
// Don't add factory node class that are not a main asset from pipelines
if (!ValidCardClasses.Contains(FactoryNode->GetClass())
&& NodeObjectClass)
{
bool bClassIsSupported = false;
for (UClass* SupportedClass : PipelineSupportAssetClasses)
{
if (NodeObjectClass->IsChildOf(SupportedClass))
{
bClassIsSupported = true;
break;
}
}
if (bClassIsSupported)
{
ValidCardClasses.Add(FactoryNode->GetClass());
if (!EnableDataPerFactoryNodeClass.Contains(FactoryNode->GetClass()))
{
//Add a new entry that is enabled by default
EnableDataPerFactoryNodeClass.FindOrAdd(FactoryNode->GetClass()).ObjectClass = FactoryNode->GetObjectClass();
}
}
}
});
//Remove class card that do not exist anymore
TArray<UClass*> CardsClassesToRemove;
for (TPair<UClass*, FFactoryNodeEnabledData>& FactoryNodeClassAndEnableStatus : EnableDataPerFactoryNodeClass)
{
if (!ValidCardClasses.Contains(FactoryNodeClassAndEnableStatus.Key))
{
CardsClassesToRemove.Add(FactoryNodeClassAndEnableStatus.Key);
}
}
for (UClass* CardToRemove : CardsClassesToRemove)
{
EnableDataPerFactoryNodeClass.Remove(CardToRemove);
}
}
void SInterchangePipelineConfigurationDialog::FillAssetCardsList()
{
//Update the pipeline supported asset class
UpdatePipelineSupportedAssetClasses();
//Update the preview container
constexpr bool bUpdateCardsTrue = true;
UpdatePreviewContainer(bUpdateCardsTrue);
if (ensure(PreviewNodeContainer))
{
UpdateEnableDataPerFactoryNodeClass();
AssetCards.Reset();
for (TPair<UClass*, FFactoryNodeEnabledData>& FactoryNodeClassAndEnableStatus : EnableDataPerFactoryNodeClass)
{
UClass* FactoryNodeClass = FactoryNodeClassAndEnableStatus.Key;
UClass* AssetClass = FactoryNodeClassAndEnableStatus.Value.ObjectClass;
if (AssetClass)
{
TSharedPtr<SInterchangeAssetCard> AssetCard = SNew(SInterchangeAssetCard)
.PreviewNodeContainer(PreviewNodeContainer)
.AssetClass(AssetClass)
.ShouldImportAssetType_Lambda([this, FactoryNodeClass]()
{
return EnableDataPerFactoryNodeClass.FindChecked(FactoryNodeClass).bEnable;
})
.OnImportAssetTypeChanged_Lambda([this, FactoryNodeClass](bool bNewEnabledValue)
{
EnableDataPerFactoryNodeClass.FindChecked(FactoryNodeClass).bEnable = bNewEnabledValue;
});
if (GInterchangeShowConflictWarningsOnCardsView
&& PipelineListViewItems.Num() > 0)
{
for (TSharedPtr<FInterchangePipelineItemType>& PipelineListViewItem : PipelineListViewItems)
{
if (AssetCard->RefreshHasConflicts(PipelineListViewItem->ConflictInfos))
{
break;
}
}
}
AssetCards.Add(AssetCard);
}
}
}
}
void SInterchangePipelineConfigurationDialog::CreateCardsViewList()
{
FillAssetCardsList();
if (AssetCards.IsEmpty())
{
CardViewList = nullptr;
}
else
{
CardViewList = SNew(SInterchangeAssetCardList).AssetCards(&AssetCards);
}
}
void SInterchangePipelineConfigurationDialog::RefreshCardsViewList()
{
FillAssetCardsList();
if (CardViewList.IsValid())
{
CardViewList->RefreshList(PreviewNodeContainer);
}
}
UInterchangeCardsPipeline* SInterchangePipelineConfigurationDialog::GenerateTransientCardsPipeline() const
{
UInterchangeCardsPipeline* InterchangeCardsPipeline = nullptr;
if (!bReimport)
{
TArray<UClass*> DisabledNodeClasses;
for (const TPair<UClass*, FFactoryNodeEnabledData>& FactoryNodeClassAndEnableStatus : EnableDataPerFactoryNodeClass)
{
if(!FactoryNodeClassAndEnableStatus.Value.bEnable)
{
DisabledNodeClasses.Add(FactoryNodeClassAndEnableStatus.Key);
}
}
if (!DisabledNodeClasses.IsEmpty())
{
InterchangeCardsPipeline = NewObject<UInterchangeCardsPipeline>();
InterchangeCardsPipeline->SetDisabledFactoryNodes(DisabledNodeClasses);
}
}
return InterchangeCardsPipeline;
}
void SInterchangePipelineConfigurationDialog::OnFilterOptionsChanged(ECheckBoxState CheckState)
{
bool bNewCheckValue = CheckState == ECheckBoxState::Checked ? true : false;
if (bNewCheckValue == bFilterOptions)
{
//Check state did not change
return;
}
bFilterOptions = bNewCheckValue;
//Refresh the pipeline
UpdateStack(CurrentStackName);
GConfig->SetBool(TEXT("InterchangeImportDialogOptions"), TEXT("FilterOptions"), bFilterOptions, GEditorPerProjectIni);
}
void SInterchangePipelineConfigurationDialog::OnShowEssentialsChanged(ECheckBoxState CheckState)
{
bool bNewCheckValue = CheckState == ECheckBoxState::Checked ? true : false;
if (bNewCheckValue == bShowEssentials)
{
//Check state did not change
return;
}
bShowEssentials = bNewCheckValue;
//Refresh the pipeline
UpdateStack(CurrentStackName);
GConfig->SetBool(TEXT("InterchangeImportDialogOptions"), TEXT("ShowEssentials"), bShowEssentials, GEditorPerProjectIni);
}
void SInterchangePipelineConfigurationDialog::UpdatePreviewContainer(bool bUpdateCards) const
{
auto ClearObjectFlags = [](UObject* Obj)
{
Obj->ClearFlags(RF_Standalone | RF_Public);
Obj->ClearInternalFlags(EInternalObjectFlags::Async);
};
if (PreviewNodeContainer)
{
ClearObjectFlags(PreviewNodeContainer);
PreviewNodeContainer = nullptr;
}
PreviewNodeContainer = DuplicateObject<UInterchangeBaseNodeContainer>(BaseNodeContainer.Get(), GetTransientPackage());
PreviewNodeContainer->SetChildrenCache(BaseNodeContainer->GetChildrenCache());
TArray<UInterchangeSourceData*> SourceDatas;
SourceDatas.Add(SourceData.Get());
//Execute all pipelines on the duplicated container
UInterchangeResultsContainer* Results = NewObject<UInterchangeResultsContainer>(GetTransientPackage());
for (int32 PipelineIndex = 0; PipelineIndex < PipelineListViewItems.Num(); ++PipelineIndex)
{
const TSharedPtr<FInterchangePipelineItemType> PipelineItem = PipelineListViewItems[PipelineIndex];
//Duplicate the pipeline because ScriptedExecutePipeline is not const
if (UInterchangePipelineBase* DuplicatedPipeline = DuplicateObject<UInterchangePipelineBase>(PipelineItem->Pipeline, GetTransientPackage()))
{
DuplicatedPipeline->TransferAdjustSettings(PipelineItem->Pipeline);
DuplicatedPipeline->SetResultsContainer(Results);
DuplicatedPipeline->ScriptedExecutePipeline(PreviewNodeContainer, SourceDatas, FString());
ClearObjectFlags(DuplicatedPipeline);
}
}
if (!bUpdateCards)
{
//If we do not update cards execute the cards pipeline since its a final preview
if (UInterchangeCardsPipeline* InterchangeCardsPipeline = GenerateTransientCardsPipeline())
{
InterchangeCardsPipeline->ScriptedExecutePipeline(PreviewNodeContainer, SourceDatas, FString());
}
}
PreviewNodeContainer->IterateNodesOfType<UInterchangeFactoryBaseNode>([ClosureReimportObject = ReimportObject](const FString& NodeUid, UInterchangeFactoryBaseNode* Node)
{
//Set all node in preview mode so hide the internal data attributes
Node->UserInterfaceContext = EInterchangeNodeUserInterfaceContext::Preview;
//If we reimport a specific object we want to disabled all factory nodes that are not supporting the reimport object class
if (ClosureReimportObject.IsValid())
{
Node->SetEnabled(ClosureReimportObject.Get()->IsA(Node->GetObjectClass()));
}
});
//Make sure all temporary object are not flags to persist
ClearObjectFlags(Results);
}
FReply SInterchangePipelineConfigurationDialog::OnPreviewImport() const
{
auto ClearObjectFlags = [](UObject* Obj)
{
Obj->ClearFlags(RF_Standalone | RF_Public);
Obj->ClearInternalFlags(EInternalObjectFlags::Async);
};
constexpr bool bUpdateCardsFalse = false;
UpdatePreviewContainer(bUpdateCardsFalse);
if (!ensure(PreviewNodeContainer))
{
return FReply::Handled();
}
//Create and show the graph inspector UI dialog
TSharedRef<SWindow> Window = SNew(SWindow)
.ClientSize(FVector2D(800.f, 650.f))
.Title(NSLOCTEXT("SInterchangePipelineConfigurationDialog", "InterchangePreviewTitle", "Interchange Preview"));
TSharedPtr<SInterchangeGraphInspectorWindow> InterchangeGraphInspectorWindow;
Window->SetContent
(
SAssignNew(InterchangeGraphInspectorWindow, SInterchangeGraphInspectorWindow)
.InterchangeBaseNodeContainer(PreviewNodeContainer)
.bPreview(true)
.OwnerWindow(Window)
);
FSlateApplication::Get().AddModalWindow(Window, OwnerWindow.Pin(), false);
return FReply::Handled();
}
#undef LOCTEXT_NAMESPACE