/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ #include #include #include #include #include #include #include #include AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT #include #include #include #include #include #include #include #include #include #include #include AZ_POP_DISABLE_WARNING namespace AZ { namespace Render { namespace EditorMaterialComponentExporter { AZStd::string GetLabelByAssetId(const AZ::Data::AssetId& assetId) { AZStd::string label; if (assetId.IsValid()) { // Material assets that are exported through the scene pipeline have their filenames generated by adding // the DCC material name as a prefix and a unique number to the end of the source file name. // Rather than storing the DCC material name inside of the material asset we can reproduce it by removing // the prefix and suffix from the product file name. // We need the material product path as the initial string that will be stripped down const AZStd::string& productPath = AZ::RPI::AssetUtils::GetProductPathByAssetId(assetId); if (!productPath.empty() && AzFramework::StringFunc::Path::GetFileName(productPath.c_str(), label)) { // If there is a source file, typically an FBX or other model file, we must get its filename to remove the prefix from the label AZStd::string prefix; const AZStd::string& sourcePath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(assetId); if (!sourcePath.empty() && AZ::StringFunc::Path::GetFileName(sourcePath.c_str(), prefix)) { if (!prefix.empty() && prefix.size() < label.size()) { if (AZ::StringFunc::StartsWith(label, prefix, false)) { // All of the product filename's tokens are separated by underscores so we must also remove the first underscore after the prefix label = label.substr(prefix.size() + 1); } } } // We can remove the numeric suffix by stripping the label of everything after the last underscore const auto iter = label.find_last_of("_"); if (iter != AZStd::string::npos) { label = label.substr(0, iter); } } } return label; } AZStd::string GetExportPathByAssetId(const AZ::Data::AssetId& assetId) { AZStd::string exportPath; if (assetId.IsValid()) { exportPath = AZ::RPI::AssetUtils::GetSourcePathByAssetId(assetId); AZ::StringFunc::Path::StripExtension(exportPath); exportPath += "_"; exportPath += GetLabelByAssetId(assetId); exportPath += "."; exportPath += AZ::RPI::MaterialSourceData::Extension; AZ::StringFunc::Path::Normalize(exportPath); } return exportPath; } bool OpenExportDialog(ExportItemsContainer& exportItems) { QWidget* activeWindow = nullptr; AzToolsFramework::EditorWindowRequestBus::BroadcastResult(activeWindow, &AzToolsFramework::EditorWindowRequests::GetAppMainWindow); // Constructing a dialog with a table to display all configurable material export items QDialog dialog(activeWindow); dialog.setWindowTitle("Generate/Manage Source Materials"); const QStringList headerLabels = { "Material Slot", "Material Filename", "Overwrite" }; const int MaterialSlotColumn = 0; const int MaterialFileColumn = 1; const int OverwriteFileColumn = 2; // Create a table widget that will be filled with all of the data and options for each exported material QTableWidget* tableWidget = new QTableWidget(&dialog); tableWidget->setColumnCount(headerLabels.size()); tableWidget->setRowCount((int)exportItems.size()); tableWidget->setHorizontalHeaderLabels(headerLabels); tableWidget->setSortingEnabled(false); tableWidget->setAlternatingRowColors(true); tableWidget->setCornerButtonEnabled(false); tableWidget->setContextMenuPolicy(Qt::DefaultContextMenu); tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows); tableWidget->setSelectionMode(QAbstractItemView::SingleSelection); tableWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); // Force the table to stretch its header to fill the entire width of the dialog tableWidget->horizontalHeader()->setSectionResizeMode(MaterialSlotColumn, QHeaderView::ResizeToContents); tableWidget->horizontalHeader()->setSectionResizeMode(MaterialFileColumn, QHeaderView::Stretch); tableWidget->horizontalHeader()->setSectionResizeMode(OverwriteFileColumn, QHeaderView::ResizeToContents); tableWidget->horizontalHeader()->setStretchLastSection(false); // Hide row numbers tableWidget->verticalHeader()->setVisible(false); int row = 0; for (ExportItem& exportItem : exportItems) { QFileInfo fileInfo(GetExportPathByAssetId(exportItem.m_originalAssetId).c_str()); // Configuring initial settings based on whether or not the target file already exists exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData(); exportItem.m_exists = fileInfo.exists(); exportItem.m_overwrite = false; // Populate the table with data for every column tableWidget->setItem(row, MaterialSlotColumn, new QTableWidgetItem()); tableWidget->setItem(row, MaterialFileColumn, new QTableWidgetItem()); tableWidget->setItem(row, OverwriteFileColumn, new QTableWidgetItem()); // Create a check box for toggling the enabled state of this item QCheckBox* materialSlotCheckBox = new QCheckBox(tableWidget); materialSlotCheckBox->setChecked(exportItem.m_enabled); materialSlotCheckBox->setText(GetLabelByAssetId(exportItem.m_originalAssetId).c_str()); tableWidget->setCellWidget(row, MaterialSlotColumn, materialSlotCheckBox); // Create a file picker widget for selecting the save path for the exported material AzQtComponents::BrowseEdit* materialFileWidget = new AzQtComponents::BrowseEdit(tableWidget); materialFileWidget->setLineEditReadOnly(true); materialFileWidget->setClearButtonEnabled(false); materialFileWidget->setEnabled(exportItem.m_enabled); materialFileWidget->setText(fileInfo.fileName()); tableWidget->setCellWidget(row, MaterialFileColumn, materialFileWidget); // Create a check box for toggling the overwrite state of this item QWidget* overwriteCheckBoxContainer = new QWidget(tableWidget); QCheckBox* overwriteCheckBox = new QCheckBox(overwriteCheckBoxContainer); overwriteCheckBox->setChecked(exportItem.m_overwrite); overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists); overwriteCheckBoxContainer->setLayout(new QHBoxLayout(overwriteCheckBoxContainer)); overwriteCheckBoxContainer->layout()->addWidget(overwriteCheckBox); overwriteCheckBoxContainer->layout()->setAlignment(Qt::AlignCenter); overwriteCheckBoxContainer->layout()->setContentsMargins(0, 0, 0, 0); tableWidget->setCellWidget(row, OverwriteFileColumn, overwriteCheckBoxContainer); // Whenever the selection is updated, automatically apply the change to the export item QObject::connect(materialSlotCheckBox, &QCheckBox::stateChanged, materialSlotCheckBox, [&exportItem, materialFileWidget, materialSlotCheckBox, overwriteCheckBox]([[maybe_unused]] int state) { exportItem.m_enabled = materialSlotCheckBox->isChecked(); materialFileWidget->setEnabled(exportItem.m_enabled); overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists); }); // Whenever the overwrite check box is updated, automatically apply the change to the export item QObject::connect(overwriteCheckBox, &QCheckBox::stateChanged, overwriteCheckBox, [&exportItem, overwriteCheckBox]([[maybe_unused]] int state) { exportItem.m_overwrite = overwriteCheckBox->isChecked(); }); // Whenever the browse button is clicked, open a save file dialog in the same location as the current export file setting QObject::connect(materialFileWidget, &AzQtComponents::BrowseEdit::attachedButtonTriggered, materialFileWidget, [&dialog, &exportItem, materialFileWidget, overwriteCheckBox]() { QFileInfo fileInfo = QFileDialog::getSaveFileName(&dialog, QString("Select Material Filename"), exportItem.m_exportPath.c_str(), QString("Material (*.material)"), nullptr, QFileDialog::DontConfirmOverwrite); // Only update the export data if a valid path and filename was selected if (!fileInfo.absoluteFilePath().isEmpty()) { exportItem.m_exportPath = fileInfo.absoluteFilePath().toUtf8().constData(); exportItem.m_exists = fileInfo.exists(); exportItem.m_overwrite = fileInfo.exists(); // Update the controls to display the new state materialFileWidget->setText(fileInfo.fileName()); overwriteCheckBox->setChecked(exportItem.m_overwrite); overwriteCheckBox->setEnabled(exportItem.m_enabled && exportItem.m_exists); } }); ++row; } tableWidget->sortItems(MaterialSlotColumn); // Create the bottom row of the dialog with action buttons for exporting or canceling the operation QDialogButtonBox* buttonBox = new QDialogButtonBox(&dialog); buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); QObject::connect(buttonBox, &QDialogButtonBox::accepted, &dialog, &QDialog::accept); QObject::connect(buttonBox, &QDialogButtonBox::rejected, &dialog, &QDialog::reject); // Create a heading label for the top of the dialog QLabel* labelWidget = new QLabel("\nSelect the material slots that you want to generate new source materials for. Edit the material file name and location using the file picker.\n", &dialog); labelWidget->setWordWrap(true); QVBoxLayout* dialogLayout = new QVBoxLayout(&dialog); dialogLayout->addWidget(labelWidget); dialogLayout->addWidget(tableWidget); dialogLayout->addWidget(buttonBox); dialog.setLayout(dialogLayout); dialog.setModal(true); // Forcing the initial dialog size to accomodate typical content. // Temporarily settng fixed size because dialog.show/exec invokes WindowDecorationWrapper::showEvent. // This forces the dialog to be centered and sized based on the layout of content. // Resizing the dialog after show will not be centered and moving the dialog programatically doesn't m0ve the custmk frame. dialog.setFixedSize(500, 200); dialog.show(); // Removing fixed size to allow drag resizing dialog.setMinimumSize(0, 0); dialog.setMaximumSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX); // Return true if the user press the export button return dialog.exec() == QDialog::Accepted; } bool ExportMaterialSourceData(const ExportItem& exportItem) { if (!exportItem.m_enabled || exportItem.m_exportPath.empty()) { return false; } if (exportItem.m_exists && !exportItem.m_overwrite) { return true; } EditorMaterialComponentUtil::MaterialEditData editData; if (!EditorMaterialComponentUtil::LoadMaterialEditDataFromAssetId(exportItem.m_originalAssetId, editData)) { AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to load material data."); return false; } if (!EditorMaterialComponentUtil::SaveSourceMaterialFromEditData(exportItem.m_exportPath, editData)) { AZ_Warning("AZ::Render::EditorMaterialComponentExporter", false, "Failed to save material data."); return false; } return true; } } // namespace EditorMaterialComponentExporter } // namespace Render } // namespace AZ