// Copyright Epic Games, Inc. All Rights Reserved. #include "Fbx/SSceneMaterialsListView.h" #include "Modules/ModuleManager.h" #include "Framework/Application/SlateApplication.h" #include "Textures/SlateIcon.h" #include "Framework/Commands/UIAction.h" #include "Framework/MultiBox/MultiBoxBuilder.h" #include "Widgets/Input/SCheckBox.h" #include "EditorStyleSet.h" #include "Materials/MaterialInterface.h" #include "Materials/Material.h" #include "AssetData.h" #include "IContentBrowserSingleton.h" #include "ContentBrowserModule.h" #include "Fbx/SSceneImportNodeTreeView.h" #include "FbxImporter.h" #include "PackageTools.h" #define LOCTEXT_NAMESPACE "SFbxSceneMaterialsListView" const FName MaterialCheckBoxSelectionHeaderIdName(TEXT("CheckBoxSelectionHeaderId")); const FName MaterialNameHeaderIdName(TEXT("AssetNameHeaderId")); const FName MaterialContentPathHeaderIdName(TEXT("ContentPathHeaderId")); const FName MaterialStatusNameHeaderIdName(TEXT("OptionsNameHeaderId")); /** The item used for visualizing the class in the tree. */ class SFbxMaterialItemTableListViewRow: public SMultiColumnTableRow { public: SLATE_BEGIN_ARGS(SFbxMaterialItemTableListViewRow) : _FbxMaterialInfo(nullptr) {} /** The item content. */ SLATE_ARGUMENT(FbxMaterialInfoPtr, FbxMaterialInfo) SLATE_END_ARGS() /** * Construct the widget * * @param InArgs A declaration from which to construct the widget */ void Construct(const FArguments& InArgs, const TSharedRef& InOwnerTableView) { FbxMaterialInfo = InArgs._FbxMaterialInfo; // This is suppose to always be valid check(FbxMaterialInfo.IsValid()); SMultiColumnTableRow::Construct( FSuperRowType::FArguments() .Style(FEditorStyle::Get(), "DataTableEditor.CellListViewRow"), InOwnerTableView); } /** Overridden from SMultiColumnTableRow. Generates a widget for this column of the list view. */ virtual TSharedRef GenerateWidgetForColumn(const FName& ColumnName) override { if (ColumnName == MaterialCheckBoxSelectionHeaderIdName) { return SNew(SBox) .HAlign(HAlign_Center) [SNew(SCheckBox) .OnCheckStateChanged(this, &SFbxMaterialItemTableListViewRow::OnItemCheckChanged) .IsChecked(this, &SFbxMaterialItemTableListViewRow::IsItemChecked)]; } else if (ColumnName == MaterialNameHeaderIdName) { return SNew(STextBlock) .Text(FText::FromString(FbxMaterialInfo->Name)) .ToolTipText(FText::FromString(FbxMaterialInfo->Name)); } else if (ColumnName == MaterialContentPathHeaderIdName) { return SNew(STextBlock) .Text(this, &SFbxMaterialItemTableListViewRow::GetAssetFullName) .ColorAndOpacity(this, &SFbxMaterialItemTableListViewRow::GetContentPathTextColor) .ToolTipText(this, &SFbxMaterialItemTableListViewRow::GetAssetFullName); } else if (ColumnName == MaterialStatusNameHeaderIdName) { return SNew(STextBlock) .Text(this, &SFbxMaterialItemTableListViewRow::GetAssetStatus) .ToolTipText(this, &SFbxMaterialItemTableListViewRow::GetAssetStatus); } return SNullWidget::NullWidget; } private: void OnItemCheckChanged(ECheckBoxState CheckType) { if (!FbxMaterialInfo.IsValid()) return; FbxMaterialInfo->bImportAttribute = CheckType == ECheckBoxState::Checked; } ECheckBoxState IsItemChecked() const { return FbxMaterialInfo->bImportAttribute ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } FSlateColor GetContentPathTextColor() const { return FbxMaterialInfo->bOverridePath ? FLinearColor(0.75f, 0.75f, 0.0f, 1.0f) : FSlateColor::UseForeground(); } FText GetAssetFullName() const { return FText::FromString(FbxMaterialInfo->GetFullImportName()); } FText GetAssetStatus() const { // Is the material already exist if (FbxMaterialInfo->GetContentObject() != nullptr) { return FText::FromString(TEXT("Use Existing")); } else { return FText::FromString(TEXT("Create")); } } /** The node info to build the tree view row from. */ FbxMaterialInfoPtr FbxMaterialInfo; }; ////////////////////////////////////////////////////////////////////////// // Materials List SFbxSceneMaterialsListView::~SFbxSceneMaterialsListView() { SceneInfo = nullptr; SceneInfoOriginal = nullptr; GlobalImportSettings = nullptr; MaterialsArray.Reset(); TexturesArray = nullptr; FullPath.Empty(); IsReimport = false; CreateContentFolderHierarchy = false; } void SFbxSceneMaterialsListView::GetMaterialsFromHierarchy(TArray& OutMaterials, TSharedPtr SceneInfoSource, bool bFillPathInformation) { OutMaterials.Reset(); for (FbxNodeInfoPtr NodeInfo: SceneInfoSource->HierarchyInfo) { // Get all the mesh material if (NodeInfo->AttributeType.Compare(TEXT("eMesh")) != 0 || NodeInfo->NodeName.Compare("RootNode") == 0) { continue; } for (TSharedPtr MaterialInfo: NodeInfo->Materials) { bool bAlreadyExist = false; for (TSharedPtr ExistingMaterialInfo: OutMaterials) { if (ExistingMaterialInfo->UniqueId == MaterialInfo->UniqueId) { bAlreadyExist = true; break; } } if (bAlreadyExist) { // This Material is already in the list continue; } if (bFillPathInformation) { FString NodeTreePath; if (CreateContentFolderHierarchy) { FbxNodeInfoPtr CurrentNode = NodeInfo->ParentNodeInfo; while (CurrentNode.IsValid()) { if (CurrentNode->NodeName.Compare("RootNode") != 0) { FString StringToInsert = TEXT("/") + CurrentNode->NodeName; NodeTreePath.InsertAt(0, StringToInsert); } CurrentNode = CurrentNode->ParentNodeInfo; } } FString AssetName = GlobalImportSettings->MaterialBasePath == NAME_None ? FullPath + NodeTreePath + TEXT("/") + MaterialInfo->Name : GlobalImportSettings->MaterialBasePath.ToString() + MaterialInfo->Name; MaterialInfo->SetOriginalImportPath(AssetName); FString OriginalFullImportName = UPackageTools::SanitizePackageName(AssetName); OriginalFullImportName = OriginalFullImportName + TEXT(".") + UPackageTools::SanitizePackageName(MaterialInfo->Name); MaterialInfo->SetOriginalFullImportName(OriginalFullImportName); } OutMaterials.Add(MaterialInfo); } } } void SFbxSceneMaterialsListView::FindMatchAndFillOverrideInformation(TArray& OldMaterials, TArray& NewMaterials) { for (FbxMaterialInfoPtr NewMaterial: NewMaterials) { for (FbxMaterialInfoPtr OldMaterial: OldMaterials) { if ((OldMaterial->Name.Compare(NewMaterial->Name) == 0) && OldMaterial->HierarchyPath.Compare(NewMaterial->HierarchyPath) == 0) { // Only copy the override attribute and the import flag NewMaterial->bImportAttribute = OldMaterial->bImportAttribute; NewMaterial->SetOverridePath(OldMaterial->bOverridePath); NewMaterial->OverrideImportPath = OldMaterial->OverrideImportPath; NewMaterial->OverrideFullImportName = OldMaterial->OverrideFullImportName; } } } } void SFbxSceneMaterialsListView::Construct(const SFbxSceneMaterialsListView::FArguments& InArgs) { SceneInfo = InArgs._SceneInfo; SceneInfoOriginal = InArgs._SceneInfoOriginal; GlobalImportSettings = InArgs._GlobalImportSettings; TexturesArray = InArgs._TexturesArray; FullPath = InArgs._FullPath; IsReimport = InArgs._IsReimport; CreateContentFolderHierarchy = InArgs._CreateContentFolderHierarchy; check(SceneInfo.IsValid()); check(GlobalImportSettings != nullptr); check(TexturesArray != nullptr); if (IsReimport) { check(SceneInfoOriginal.IsValid()); // We reimport this we have probably some override material // First match the previous import material with the current import material TArray OldMaterials; TArray NewMaterials; GetMaterialsFromHierarchy(OldMaterials, SceneInfoOriginal, false); GetMaterialsFromHierarchy(NewMaterials, SceneInfo, true); FindMatchAndFillOverrideInformation(OldMaterials, NewMaterials); // Copy the new material array in the one use by the list view widget MaterialsArray = NewMaterials; } else { // Fill the original information and the Materials array use by the list view widget GetMaterialsFromHierarchy(MaterialsArray, SceneInfo, true); } SListView::Construct( SListView::FArguments() .ListItemsSource(&MaterialsArray) .SelectionMode(ESelectionMode::Multi) .OnGenerateRow(this, &SFbxSceneMaterialsListView::OnGenerateRowFbxSceneListView) .OnContextMenuOpening(this, &SFbxSceneMaterialsListView::OnOpenContextMenu) .OnSelectionChanged(this, &SFbxSceneMaterialsListView::OnSelectionChanged) .HeaderRow( SNew(SHeaderRow) + SHeaderRow::Column(MaterialCheckBoxSelectionHeaderIdName) .FixedWidth(26) .DefaultLabel(FText::GetEmpty()) [SNew(SCheckBox) .HAlign(HAlign_Center) .OnCheckStateChanged(this, &SFbxSceneMaterialsListView::OnToggleSelectAll)] + SHeaderRow::Column(MaterialNameHeaderIdName) .FillWidth(300) .HAlignCell(EHorizontalAlignment::HAlign_Left) .DefaultLabel(LOCTEXT("AssetNameHeaderName", "Asset Name")) + SHeaderRow::Column(MaterialContentPathHeaderIdName) .FillWidth(300) .HAlignCell(EHorizontalAlignment::HAlign_Left) .DefaultLabel(LOCTEXT("ContentPathHeaderName", "Content Path")) + SHeaderRow::Column(MaterialStatusNameHeaderIdName) .FillWidth(60) .HAlignCell(EHorizontalAlignment::HAlign_Left) .DefaultLabel(LOCTEXT("StatusHeaderName", "Status")))); } TSharedRef SFbxSceneMaterialsListView::OnGenerateRowFbxSceneListView(FbxMaterialInfoPtr Item, const TSharedRef& OwnerTable) { TSharedRef ReturnRow = SNew(SFbxMaterialItemTableListViewRow, OwnerTable) .FbxMaterialInfo(Item); return ReturnRow; } void SFbxSceneMaterialsListView::UpdateMaterialBasePath() { TArray MaterialsUpdated; GetMaterialsFromHierarchy(MaterialsUpdated, SceneInfo, true); } TSharedPtr SFbxSceneMaterialsListView::OnOpenContextMenu() { TArray SelectedFbxMaterialInfos; int32 SelectCount = GetSelectedItems(SelectedFbxMaterialInfos); // Build up the menu for a selection const bool bCloseAfterSelection = true; FMenuBuilder MenuBuilder(bCloseAfterSelection, TSharedPtr()); // We always create a section here, even if there is no parent so that clients can still extend the menu MenuBuilder.BeginSection("FbxScene_MAT_ImportSection"); { const FSlateIcon PlusIcon(FEditorStyle::GetStyleSetName(), "Plus"); MenuBuilder.AddMenuEntry(LOCTEXT("CheckForImport", "Add Selection To Import"), FText(), PlusIcon, FUIAction(FExecuteAction::CreateSP(this, &SFbxSceneMaterialsListView::AddSelectionToImport))); const FSlateIcon MinusIcon(FEditorStyle::GetStyleSetName(), "PropertyWindow.Button_RemoveFromArray"); MenuBuilder.AddMenuEntry(LOCTEXT("UncheckForImport", "Remove Selection From Import"), FText(), MinusIcon, FUIAction(FExecuteAction::CreateSP(this, &SFbxSceneMaterialsListView::RemoveSelectionFromImport))); } MenuBuilder.EndSection(); MenuBuilder.BeginSection("FbxScene_MAT_AssignSection"); { if (SelectCount == 1) { MenuBuilder.AddMenuEntry(LOCTEXT("AssignExistingMaterial", "Assign Existing Material..."), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SFbxSceneMaterialsListView::AssignMaterialToExisting))); MenuBuilder.AddMenuEntry(LOCTEXT("ResetAssignExistingMaterial", "Reset Material to Fbx content"), FText(), FSlateIcon(), FUIAction(FExecuteAction::CreateSP(this, &SFbxSceneMaterialsListView::ResetAssignMaterial))); MenuBuilder.AddMenuSeparator(); FContentBrowserModule& ContentBrowserModule = FModuleManager::Get().LoadModuleChecked(TEXT("ContentBrowser")); // Configure filter for asset picker FAssetPickerConfig Config; Config.Filter.bRecursiveClasses = true; Config.Filter.ClassNames.Add(UMaterialInterface::StaticClass()->GetFName()); Config.InitialAssetViewType = EAssetViewType::List; Config.OnAssetSelected = FOnAssetSelected::CreateSP(this, &SFbxSceneMaterialsListView::AssignMaterialAssetData); Config.bAllowNullSelection = false; Config.bFocusSearchBoxWhenOpened = true; Config.bAllowDragging = false; // Thumbnail are not working since we are in a modal dialog Config.bCanShowRealTimeThumbnails = true; // Don't show stuff in Engine Config.Filter.PackagePaths.Add("/Game"); Config.Filter.bRecursivePaths = true; TSharedRef Widget = SNew(SBox) [ContentBrowserModule.Get().CreateAssetPicker(Config)]; MenuBuilder.AddWidget(Widget, FText::GetEmpty()); } } MenuBuilder.EndSection(); return MenuBuilder.MakeWidget(); } void SFbxSceneMaterialsListView::AddSelectionToImport() { SetSelectionImportState(true); } void SFbxSceneMaterialsListView::RemoveSelectionFromImport() { SetSelectionImportState(false); } void SFbxSceneMaterialsListView::SetSelectionImportState(bool MarkForImport) { TArray SelectedFbxMaterialInfos; GetSelectedItems(SelectedFbxMaterialInfos); for (FbxMaterialInfoPtr Item: SelectedFbxMaterialInfos) { Item->bImportAttribute = MarkForImport; } } void SFbxSceneMaterialsListView::OnSelectionChanged(FbxMaterialInfoPtr Item, ESelectInfo::Type SelectionType) { // Change the texture list TArray SelectedFbxMaterialInfos; GetSelectedItems(SelectedFbxMaterialInfos); TexturesArray->Reset(); for (FbxMaterialInfoPtr SelectItem: SelectedFbxMaterialInfos) { TexturesArray->Append(SelectItem->Textures); } } void SFbxSceneMaterialsListView::OnToggleSelectAll(ECheckBoxState CheckType) { for (FbxMaterialInfoPtr MaterialInfo: MaterialsArray) { MaterialInfo->bImportAttribute = CheckType == ECheckBoxState::Checked; } } void SFbxSceneMaterialsListView::AssignMaterialAssetData(const FAssetData& AssetData) { TArray SelectedFbxMaterialInfos; int32 SelectCount = GetSelectedItems(SelectedFbxMaterialInfos); if (SelectCount != 1) { FSlateApplication::Get().DismissAllMenus(); return; } UPackage* ContentPackage = AssetData.GetPackage(); UObject* ContentObject = AssetData.GetAsset(); if (ContentObject != nullptr) { if (!ContentObject->HasAnyFlags(RF_Transient) && !ContentObject->IsPendingKill()) { for (FbxMaterialInfoPtr ItemPtr: SelectedFbxMaterialInfos) { // Override the MeshInfo with the new asset path ItemPtr->SetOverridePath(true); ItemPtr->OverrideImportPath = AssetData.PackageName.ToString(); ItemPtr->OverrideFullImportName = AssetData.ObjectPath.ToString(); } } } FSlateApplication::Get().DismissAllMenus(); } void SFbxSceneMaterialsListView::AssignMaterialToExisting() { TArray SelectedFbxMaterialInfos; int32 SelectCount = GetSelectedItems(SelectedFbxMaterialInfos); if (SelectCount != 1) return; FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); for (FbxMaterialInfoPtr ItemPtr: SelectedFbxMaterialInfos) { FOpenAssetDialogConfig SelectAssetConfig; SelectAssetConfig.DialogTitleOverride = LOCTEXT("FbxChooseMaterialAssetContentPath", "Choose a material asset"); // SelectAssetConfig.DefaultPath = ItemPtr->OriginalImportPath; SelectAssetConfig.bAllowMultipleSelection = false; SelectAssetConfig.AssetClassNames.Add(UMaterial::StaticClass()->GetFName()); TArray AssetData = ContentBrowserModule.Get().CreateModalOpenAssetDialog(SelectAssetConfig); if (AssetData.Num() == 1) { UPackage* ContentPackage = AssetData[0].GetPackage(); UObject* ContentObject = AssetData[0].GetAsset(); if (ContentObject != nullptr) { if (!ContentObject->HasAnyFlags(RF_Transient) && !ContentObject->IsPendingKill()) { // Override the MeshInfo with the new asset path ItemPtr->SetOverridePath(true); ItemPtr->OverrideImportPath = AssetData[0].PackageName.ToString(); ItemPtr->OverrideFullImportName = AssetData[0].ObjectPath.ToString(); } } } } } void SFbxSceneMaterialsListView::ResetAssignMaterial() { TArray SelectedFbxMaterialInfos; int32 SelectCount = GetSelectedItems(SelectedFbxMaterialInfos); for (FbxMaterialInfoPtr ItemPtr: SelectedFbxMaterialInfos) { if (ItemPtr->bOverridePath) { ItemPtr->SetOverridePath(false); ItemPtr->OverrideImportPath.Empty(); ItemPtr->OverrideFullImportName.Empty(); } } } #undef LOCTEXT_NAMESPACE