You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Material/EditorMaterialComponentExpo...

276 lines
15 KiB
C++

/*
* 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 <Material/EditorMaterialComponentExporter.h>
#include <Material/EditorMaterialComponentUtil.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzQtComponents/Components/Widgets/BrowseEdit.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/EditorWindowRequestBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QApplication>
#include <QCheckBox>
#include <QComboBox>
#include <QDialogButtonBox>
#include <QFileDialog>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QPushButton>
#include <QTableWidget>
#include <QVBoxLayout>
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