Continues the work of the material subsystem improvements. This merge covers the continued development of the material editor. The primary improvements are the addition of new data types, a new appearance preview UI, and changes in the array data types. New data types were added to support more advanced workflows, such as the Render Workbench.The Image datatype allows the material to embed the image in the card instead of pointing to an image in an external file. Multi-buyte strings span multiple lines as the name implies. It preserves formatting accross those lines. Also several list types are now supported, with the primary difference being the editors. List is a list of strings, FileList is a list of file path names, and ImageList is a list of embedded images. For the appearance preview, the UI now uses the same Coin library as is used in the documents, meaning the preview will look exactly the same as the material will be shown in the documents. The array data types are now more complete. The default value wasn't being used as originially envisioned and was tehrefore removed. For 3D arrays, the Python API was implemented. There were a lot of code clean ups. This involved removing logging statements used for debugging during development, reduction of lint warnings, and code refactoring. The editor can automatically convert from previous format files to the current format. This has been extended to material files generated by the Render WB. Old format files are displayed in the editor with a warning icon. Selecting one will require saving the file in the new format before it can be used.
1216 lines
38 KiB
C++
1216 lines
38 KiB
C++
/***************************************************************************
|
|
* Copyright (c) 2023 David Carter <dcarter@david.carter.ca> *
|
|
* *
|
|
* This file is part of FreeCAD. *
|
|
* *
|
|
* FreeCAD is free software: you can redistribute it and/or modify it *
|
|
* under the terms of the GNU Lesser General Public License as *
|
|
* published by the Free Software Foundation, either version 2.1 of the *
|
|
* License, or (at your option) any later version. *
|
|
* *
|
|
* FreeCAD is distributed in the hope that it will be useful, but *
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
|
* Lesser General Public License for more details. *
|
|
* *
|
|
* You should have received a copy of the GNU Lesser General Public *
|
|
* License along with FreeCAD. If not, see *
|
|
* <https://www.gnu.org/licenses/>. *
|
|
* *
|
|
**************************************************************************/
|
|
|
|
#include "PreCompiled.h"
|
|
#ifndef _PreComp_
|
|
#include <QColorDialog>
|
|
#include <QDesktopServices>
|
|
#include <QIODevice>
|
|
#include <QItemSelectionModel>
|
|
#include <QMenu>
|
|
#include <QMessageBox>
|
|
#include <QString>
|
|
#include <QStringList>
|
|
#include <QTextStream>
|
|
#include <QVariant>
|
|
#endif
|
|
|
|
#include <limits>
|
|
|
|
#include <App/Application.h>
|
|
#include <App/License.h>
|
|
#include <Base/Interpreter.h>
|
|
#include <Base/Quantity.h>
|
|
#include <Gui/Application.h>
|
|
#include <Gui/Command.h>
|
|
#include <Gui/InputField.h>
|
|
#include <Gui/PrefWidgets.h>
|
|
#include <Gui/SpinBox.h>
|
|
#include <Gui/WaitCursor.h>
|
|
|
|
#include <Mod/Material/App/Exceptions.h>
|
|
#include <Mod/Material/App/ModelManager.h>
|
|
|
|
#include "MaterialDelegate.h"
|
|
#include "MaterialSave.h"
|
|
#include "MaterialsEditor.h"
|
|
#include "ModelSelect.h"
|
|
#include "ui_MaterialsEditor.h"
|
|
|
|
|
|
using namespace MatGui;
|
|
|
|
/* TRANSLATOR MatGui::MaterialsEditor */
|
|
|
|
MaterialsEditor::MaterialsEditor(QWidget* parent)
|
|
: QDialog(parent)
|
|
, ui(new Ui_MaterialsEditor)
|
|
, _material(std::make_shared<Materials::Material>())
|
|
, _edited(false)
|
|
, _rendered(nullptr)
|
|
, _recentMax(0)
|
|
{
|
|
ui->setupUi(this);
|
|
|
|
_warningIcon = QIcon(QString::fromStdString(":/icons/Warning.svg"));
|
|
|
|
getFavorites();
|
|
getRecents();
|
|
|
|
createMaterialTree();
|
|
createPhysicalTree();
|
|
createAppearanceTree();
|
|
createPreviews();
|
|
setMaterialDefaults();
|
|
|
|
// Reset to previous size
|
|
auto param = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Material/Editor");
|
|
auto width = param->GetInt("EditorWidth", 835);
|
|
auto height = param->GetInt("EditorHeight", 542);
|
|
|
|
resize(width, height);
|
|
|
|
ui->buttonURL->setIcon(QIcon(QString::fromStdString(":/icons/internet-web-browser.svg")));
|
|
|
|
connect(ui->standardButtons->button(QDialogButtonBox::Ok),
|
|
&QPushButton::clicked,
|
|
this,
|
|
&MaterialsEditor::onOk);
|
|
connect(ui->standardButtons->button(QDialogButtonBox::Cancel),
|
|
&QPushButton::clicked,
|
|
this,
|
|
&MaterialsEditor::onCancel);
|
|
connect(ui->standardButtons->button(QDialogButtonBox::Save),
|
|
&QPushButton::clicked,
|
|
this,
|
|
&MaterialsEditor::onSave);
|
|
|
|
connect(ui->editName, &QLineEdit::textEdited, this, &MaterialsEditor::onName);
|
|
connect(ui->editAuthor, &QLineEdit::textEdited, this, &MaterialsEditor::onAuthor);
|
|
connect(ui->editLicense, &QLineEdit::textEdited, this, &MaterialsEditor::onLicense);
|
|
connect(ui->editSourceURL, &QLineEdit::textEdited, this, &MaterialsEditor::onSourceURL);
|
|
connect(ui->editSourceReference,
|
|
&QLineEdit::textEdited,
|
|
this,
|
|
&MaterialsEditor::onSourceReference);
|
|
connect(ui->editDescription, &QTextEdit::textChanged, this, &MaterialsEditor::onDescription);
|
|
|
|
connect(ui->buttonURL, &QPushButton::clicked, this, &MaterialsEditor::onURL);
|
|
connect(ui->buttonPhysicalAdd, &QPushButton::clicked, this, &MaterialsEditor::onPhysicalAdd);
|
|
connect(ui->buttonPhysicalRemove,
|
|
&QPushButton::clicked,
|
|
this,
|
|
&MaterialsEditor::onPhysicalRemove);
|
|
connect(ui->buttonAppearanceAdd,
|
|
&QPushButton::clicked,
|
|
this,
|
|
&MaterialsEditor::onAppearanceAdd);
|
|
connect(ui->buttonAppearanceRemove,
|
|
&QPushButton::clicked,
|
|
this,
|
|
&MaterialsEditor::onAppearanceRemove);
|
|
connect(ui->buttonInheritNew,
|
|
&QPushButton::clicked,
|
|
this,
|
|
&MaterialsEditor::onInheritNewMaterial);
|
|
connect(ui->buttonNew, &QPushButton::clicked, this, &MaterialsEditor::onNewMaterial);
|
|
connect(ui->buttonFavorite, &QPushButton::clicked, this, &MaterialsEditor::onFavourite);
|
|
|
|
QItemSelectionModel* selectionModel = ui->treeMaterials->selectionModel();
|
|
connect(selectionModel,
|
|
&QItemSelectionModel::selectionChanged,
|
|
this,
|
|
&MaterialsEditor::onSelectMaterial);
|
|
connect(ui->treeMaterials, &QTreeView::doubleClicked, this, &MaterialsEditor::onDoubleClick);
|
|
|
|
ui->treeMaterials->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
connect(ui->treeMaterials,
|
|
&QWidget::customContextMenuRequested,
|
|
this,
|
|
&MaterialsEditor::onContextMenu);
|
|
}
|
|
|
|
void MaterialsEditor::getFavorites()
|
|
{
|
|
_favorites.clear();
|
|
|
|
auto param = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Material/Favorites");
|
|
int count = param->GetInt("Favorites", 0);
|
|
for (int i = 0; i < count; i++) {
|
|
QString key = QString::fromLatin1("FAV%1").arg(i);
|
|
QString uuid = QString::fromStdString(param->GetASCII(key.toStdString().c_str(), ""));
|
|
_favorites.push_back(uuid);
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::saveFavorites()
|
|
{
|
|
auto param = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Material/Favorites");
|
|
|
|
// Clear out the existing favorites
|
|
int count = param->GetInt("Favorites", 0);
|
|
for (int i = 0; i < count; i++) {
|
|
QString key = QString::fromLatin1("FAV%1").arg(i);
|
|
param->RemoveASCII(key.toStdString().c_str());
|
|
}
|
|
|
|
// Add the current values
|
|
param->SetInt("Favorites", _favorites.size());
|
|
int j = 0;
|
|
for (auto& favorite : _favorites) {
|
|
QString key = QString::fromLatin1("FAV%1").arg(j);
|
|
param->SetASCII(key.toStdString().c_str(), favorite.toStdString());
|
|
|
|
j++;
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::addFavorite(const QString& uuid)
|
|
{
|
|
// Ensure it is a material. New, unsaved materials will not be
|
|
try {
|
|
auto material = _materialManager.getMaterial(uuid);
|
|
Q_UNUSED(material)
|
|
}
|
|
catch (const Materials::MaterialNotFound&) {
|
|
return;
|
|
}
|
|
|
|
if (!isFavorite(uuid)) {
|
|
_favorites.push_back(uuid);
|
|
saveFavorites();
|
|
refreshMaterialTree();
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::removeFavorite(const QString& uuid)
|
|
{
|
|
if (isFavorite(uuid)) {
|
|
_favorites.remove(uuid);
|
|
saveFavorites();
|
|
refreshMaterialTree();
|
|
}
|
|
}
|
|
|
|
bool MaterialsEditor::isFavorite(const QString& uuid) const
|
|
{
|
|
for (auto& it : _favorites) {
|
|
if (it == uuid) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
void MaterialsEditor::getRecents()
|
|
{
|
|
_recents.clear();
|
|
|
|
auto param = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Material/Recent");
|
|
_recentMax = param->GetInt("RecentMax", 5);
|
|
int count = param->GetInt("Recent", 0);
|
|
for (int i = 0; i < count; i++) {
|
|
QString key = QString::fromLatin1("MRU%1").arg(i);
|
|
QString uuid = QString::fromStdString(param->GetASCII(key.toStdString().c_str(), ""));
|
|
_recents.push_back(uuid);
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::saveRecents()
|
|
{
|
|
auto param = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Material/Recent");
|
|
|
|
// Clear out the existing favorites
|
|
int count = param->GetInt("Recent", 0);
|
|
for (int i = 0; i < count; i++) {
|
|
QString key = QString::fromLatin1("MRU%1").arg(i);
|
|
param->RemoveASCII(key.toStdString().c_str());
|
|
}
|
|
|
|
// Add the current values
|
|
int size = _recents.size();
|
|
if (size > _recentMax) {
|
|
size = _recentMax;
|
|
}
|
|
param->SetInt("Recent", size);
|
|
int j = 0;
|
|
for (auto& recent : _recents) {
|
|
QString key = QString::fromLatin1("MRU%1").arg(j);
|
|
param->SetASCII(key.toStdString().c_str(), recent.toStdString());
|
|
|
|
j++;
|
|
if (j >= size) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::addRecent(const QString& uuid)
|
|
{
|
|
// Ensure it is a material. New, unsaved materials will not be
|
|
try {
|
|
auto material = _materialManager.getMaterial(uuid);
|
|
Q_UNUSED(material)
|
|
}
|
|
catch (const Materials::MaterialNotFound&) {
|
|
return;
|
|
}
|
|
|
|
// Ensure no duplicates
|
|
if (isRecent(uuid)) {
|
|
_recents.remove(uuid);
|
|
}
|
|
|
|
_recents.push_front(uuid);
|
|
while (_recents.size() > static_cast<std::size_t>(_recentMax)) {
|
|
_recents.pop_back();
|
|
}
|
|
|
|
saveRecents();
|
|
}
|
|
|
|
bool MaterialsEditor::isRecent(const QString& uuid) const
|
|
{
|
|
for (auto& it : _recents) {
|
|
if (it == uuid) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MaterialsEditor::onName(const QString& text)
|
|
{
|
|
_material->setName(text);
|
|
}
|
|
|
|
void MaterialsEditor::onAuthor(const QString& text)
|
|
{
|
|
_material->setAuthor(text);
|
|
}
|
|
|
|
void MaterialsEditor::onLicense(const QString& text)
|
|
{
|
|
_material->setLicense(text);
|
|
}
|
|
|
|
void MaterialsEditor::onSourceURL(const QString& text)
|
|
{
|
|
_material->setURL(text);
|
|
}
|
|
|
|
void MaterialsEditor::onSourceReference(const QString& text)
|
|
{
|
|
_material->setReference(text);
|
|
}
|
|
|
|
void MaterialsEditor::onDescription()
|
|
{
|
|
_material->setDescription(ui->editDescription->toPlainText());
|
|
}
|
|
|
|
void MaterialsEditor::propertyChange(const QString& property, const QString value)
|
|
{
|
|
if (_material->hasPhysicalProperty(property)) {
|
|
_material->setPhysicalValue(property, value);
|
|
}
|
|
else if (_material->hasAppearanceProperty(property)) {
|
|
_material->setAppearanceValue(property, value);
|
|
updatePreview();
|
|
}
|
|
_edited = true;
|
|
}
|
|
|
|
void MaterialsEditor::onURL(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
QString url = ui->editSourceURL->text();
|
|
if (url.length() > 0) {
|
|
QDesktopServices::openUrl(QUrl(url, QUrl::TolerantMode));
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::onPhysicalAdd(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
ModelSelect dialog(this, Materials::ModelFilter_Physical);
|
|
dialog.setModal(true);
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
QString selected = dialog.selectedModel();
|
|
_material->addPhysical(selected);
|
|
updateMaterial();
|
|
}
|
|
else {
|
|
Base::Console().Log("No model selected\n");
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::onPhysicalRemove(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
QItemSelectionModel* selectionModel = ui->treePhysicalProperties->selectionModel();
|
|
if (selectionModel->hasSelection()) {
|
|
auto index = selectionModel->currentIndex().siblingAtColumn(0);
|
|
|
|
auto treeModel = dynamic_cast<const QStandardItemModel*>(index.model());
|
|
|
|
// Check we're the material model root.
|
|
auto item = treeModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
QString propertyName = index.data().toString();
|
|
|
|
QString uuid = _material->getModelByName(propertyName);
|
|
_material->removePhysical(uuid);
|
|
updateMaterial();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::onAppearanceAdd(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
ModelSelect dialog(this, Materials::ModelFilter_Appearance);
|
|
dialog.setModal(true);
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
QString selected = dialog.selectedModel();
|
|
_material->addAppearance(selected);
|
|
updateMaterial();
|
|
}
|
|
else {
|
|
Base::Console().Log("No model selected\n");
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::onAppearanceRemove(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
QItemSelectionModel* selectionModel = ui->treeAppearance->selectionModel();
|
|
if (selectionModel->hasSelection()) {
|
|
auto index = selectionModel->currentIndex().siblingAtColumn(0);
|
|
|
|
auto treeModel = dynamic_cast<const QStandardItemModel*>(index.model());
|
|
|
|
// Check we're the material model root.
|
|
auto item = treeModel->itemFromIndex(index);
|
|
auto group = item->parent();
|
|
if (!group) {
|
|
QString propertyName = index.data().toString();
|
|
|
|
QString uuid = _material->getModelByName(propertyName);
|
|
_material->removeAppearance(uuid);
|
|
updateMaterial();
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::onFavourite(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
auto selected = _material->getUUID();
|
|
if (isFavorite(selected)) {
|
|
removeFavorite(selected);
|
|
}
|
|
else {
|
|
addFavorite(selected);
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::setMaterialDefaults()
|
|
{
|
|
_material->setName(tr("Unnamed"));
|
|
std::string Author = App::GetApplication()
|
|
.GetParameterGroupByPath("User parameter:BaseApp/Preferences/Document")
|
|
->GetASCII("prefAuthor", "");
|
|
_material->setAuthor(QString::fromStdString(Author));
|
|
|
|
// license stuff
|
|
auto paramGrp {App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Document")};
|
|
auto index = static_cast<int>(paramGrp->GetInt("prefLicenseType", 0));
|
|
const char* name = App::licenseItems.at(index).at(App::posnOfFullName);
|
|
// const char* url = App::licenseItems.at(index).at(App::posnOfUrl);
|
|
// std::string licenseUrl = (paramGrp->GetASCII("prefLicenseUrl", url));
|
|
_material->setLicense(QString::fromStdString(name));
|
|
|
|
// Empty materials will have no parent
|
|
_materialManager.dereference(_material);
|
|
|
|
updateMaterial();
|
|
_material->resetEditState();
|
|
}
|
|
|
|
void MaterialsEditor::onNewMaterial(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
// Ensure data is saved (or discarded) before changing materials
|
|
if (_material->getEditState() != Materials::Material::ModelEdit_None) {
|
|
// Prompt the user to save or discard changes
|
|
int res = confirmSave(this);
|
|
if (res == QMessageBox::Cancel) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Create a new material
|
|
_material = std::make_shared<Materials::Material>();
|
|
setMaterialDefaults();
|
|
}
|
|
|
|
void MaterialsEditor::onInheritNewMaterial(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
// Save the current UUID to use as out parent
|
|
auto parent = _material->getUUID();
|
|
|
|
// Ensure data is saved (or discarded) before changing materials
|
|
if (_material->getEditState() != Materials::Material::ModelEdit_None) {
|
|
// Prompt the user to save or discard changes
|
|
int res = confirmSave(this);
|
|
if (res == QMessageBox::Cancel) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Create a new material
|
|
_material = std::make_shared<Materials::Material>();
|
|
_material->setParentUUID(parent);
|
|
setMaterialDefaults();
|
|
}
|
|
|
|
void MaterialsEditor::onOk(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
accept();
|
|
}
|
|
|
|
void MaterialsEditor::onCancel(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
reject();
|
|
}
|
|
|
|
void MaterialsEditor::onSave(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
|
|
saveMaterial();
|
|
}
|
|
|
|
void MaterialsEditor::saveMaterial()
|
|
{
|
|
MaterialSave dialog(_material, this);
|
|
dialog.setModal(true);
|
|
if (dialog.exec() == QDialog::Accepted) {
|
|
updateMaterialGeneral();
|
|
_material->resetEditState();
|
|
refreshMaterialTree();
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::accept()
|
|
{
|
|
if (_material->isOldFormat()) {
|
|
Base::Console().Log("*** Old Format File ***\n");
|
|
oldFormatError();
|
|
|
|
return;
|
|
}
|
|
addRecent(_material->getUUID());
|
|
saveWindow();
|
|
QDialog::accept();
|
|
}
|
|
|
|
void MaterialsEditor::oldFormatError()
|
|
{
|
|
QMessageBox box(this);
|
|
box.setIcon(QMessageBox::Warning);
|
|
box.setWindowTitle(tr("Old Format Material"));
|
|
|
|
box.setText(tr("This file is in the old material card format."));
|
|
box.setInformativeText(QObject::tr("You must save the material before using it."));
|
|
box.adjustSize(); // Silence warnings from Qt on Windows
|
|
box.exec();
|
|
}
|
|
|
|
void MaterialsEditor::reject()
|
|
{
|
|
saveWindow();
|
|
QDialog::reject();
|
|
}
|
|
|
|
void MaterialsEditor::saveWindow()
|
|
{
|
|
auto param = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Material/Editor");
|
|
param->SetInt("EditorWidth", width());
|
|
param->SetInt("EditorHeight", height());
|
|
|
|
saveMaterialTree(param);
|
|
}
|
|
|
|
void MaterialsEditor::saveMaterialTreeChildren(const Base::Reference<ParameterGrp>& param,
|
|
QTreeView* tree,
|
|
QStandardItemModel* model,
|
|
QStandardItem* item)
|
|
{
|
|
if (item->hasChildren()) {
|
|
param->SetBool(item->text().toStdString().c_str(), tree->isExpanded(item->index()));
|
|
|
|
auto treeParam = param->GetGroup(item->text().toStdString().c_str());
|
|
for (int i = 0; i < item->rowCount(); i++) {
|
|
auto child = item->child(i);
|
|
|
|
saveMaterialTreeChildren(treeParam, tree, model, child);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::saveMaterialTree(const Base::Reference<ParameterGrp>& param)
|
|
{
|
|
auto treeParam = param->GetGroup("MaterialTree");
|
|
treeParam->Clear();
|
|
|
|
auto tree = ui->treeMaterials;
|
|
auto model = static_cast<QStandardItemModel*>(tree->model());
|
|
|
|
auto root = model->invisibleRootItem();
|
|
for (int i = 0; i < root->rowCount(); i++) {
|
|
auto child = root->child(i);
|
|
// treeParam->SetBool(child->text().toStdString().c_str(),
|
|
// tree->isExpanded(child->index()));
|
|
|
|
saveMaterialTreeChildren(treeParam, tree, model, child);
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::addMaterials(
|
|
QStandardItem& parent,
|
|
const std::shared_ptr<std::map<QString, std::shared_ptr<Materials::MaterialTreeNode>>>
|
|
modelTree,
|
|
const QIcon& folderIcon,
|
|
const QIcon& icon,
|
|
const Base::Reference<ParameterGrp>& param)
|
|
{
|
|
auto childParam = param->GetGroup(parent.text().toStdString().c_str());
|
|
auto tree = ui->treeMaterials;
|
|
for (auto& mat : *modelTree) {
|
|
std::shared_ptr<Materials::MaterialTreeNode> nodePtr = mat.second;
|
|
if (nodePtr->getType() == Materials::MaterialTreeNode::DataNode) {
|
|
auto material = nodePtr->getData();
|
|
QString uuid = material->getUUID();
|
|
|
|
QIcon matIcon = icon;
|
|
if (material->isOldFormat()) {
|
|
matIcon = _warningIcon;
|
|
}
|
|
auto card = new QStandardItem(matIcon, mat.first);
|
|
card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled
|
|
| Qt::ItemIsDropEnabled);
|
|
card->setData(QVariant(uuid), Qt::UserRole);
|
|
if (material->isOldFormat()) {
|
|
card->setToolTip(tr("This card uses the old format and must be saved before use"));
|
|
}
|
|
|
|
addExpanded(tree, &parent, card);
|
|
}
|
|
else {
|
|
auto node = new QStandardItem(folderIcon, mat.first);
|
|
addExpanded(tree, &parent, node, childParam);
|
|
node->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);
|
|
auto treeMap = nodePtr->getFolder();
|
|
addMaterials(*node, treeMap, folderIcon, icon, childParam);
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::addExpanded(QTreeView* tree, QStandardItem* parent, QStandardItem* child)
|
|
{
|
|
parent->appendRow(child);
|
|
tree->setExpanded(child->index(), true);
|
|
}
|
|
|
|
void MaterialsEditor::addExpanded(QTreeView* tree,
|
|
QStandardItem* parent,
|
|
QStandardItem* child,
|
|
const Base::Reference<ParameterGrp>& param)
|
|
{
|
|
parent->appendRow(child);
|
|
|
|
// Restore to any previous expansion state
|
|
auto expand = param->GetBool(child->text().toStdString().c_str(), true);
|
|
tree->setExpanded(child->index(), expand);
|
|
}
|
|
|
|
void MaterialsEditor::addExpanded(QTreeView* tree, QStandardItemModel* parent, QStandardItem* child)
|
|
{
|
|
parent->appendRow(child);
|
|
tree->setExpanded(child->index(), true);
|
|
}
|
|
|
|
void MaterialsEditor::addExpanded(QTreeView* tree,
|
|
QStandardItemModel* parent,
|
|
QStandardItem* child,
|
|
const Base::Reference<ParameterGrp>& param)
|
|
{
|
|
parent->appendRow(child);
|
|
|
|
// Restore to any previous expansion state
|
|
auto expand = param->GetBool(child->text().toStdString().c_str(), true);
|
|
tree->setExpanded(child->index(), expand);
|
|
}
|
|
|
|
void MaterialsEditor::createPhysicalTree()
|
|
{
|
|
auto tree = ui->treePhysicalProperties;
|
|
auto model = new QStandardItemModel();
|
|
tree->setModel(model);
|
|
|
|
QStringList headers;
|
|
headers.append(tr("Property"));
|
|
headers.append(tr("Value"));
|
|
headers.append(tr("Type"));
|
|
model->setHorizontalHeaderLabels(headers);
|
|
|
|
tree->setColumnWidth(0, 250);
|
|
tree->setColumnWidth(1, 250);
|
|
tree->setColumnHidden(2, true);
|
|
|
|
tree->setHeaderHidden(false);
|
|
tree->setUniformRowHeights(false);
|
|
auto delegate = new MaterialDelegate(this);
|
|
tree->setItemDelegateForColumn(1, delegate);
|
|
|
|
connect(delegate, &MaterialDelegate::propertyChange, this, &MaterialsEditor::propertyChange);
|
|
}
|
|
|
|
void MaterialsEditor::createPreviews()
|
|
{
|
|
_rendered = new AppearancePreview();
|
|
ui->layoutAppearance->addWidget(_rendered);
|
|
|
|
updatePreview();
|
|
}
|
|
|
|
void MaterialsEditor::createAppearanceTree()
|
|
{
|
|
auto tree = ui->treeAppearance;
|
|
auto model = new QStandardItemModel();
|
|
tree->setModel(model);
|
|
|
|
QStringList headers;
|
|
headers.append(tr("Property"));
|
|
headers.append(tr("Value"));
|
|
headers.append(tr("Type"));
|
|
model->setHorizontalHeaderLabels(headers);
|
|
|
|
tree->setColumnWidth(0, 250);
|
|
tree->setColumnWidth(1, 250);
|
|
tree->setColumnHidden(2, true);
|
|
|
|
tree->setHeaderHidden(false);
|
|
tree->setUniformRowHeights(false);
|
|
auto delegate = new MaterialDelegate(this);
|
|
tree->setItemDelegateForColumn(1, delegate);
|
|
|
|
connect(delegate, &MaterialDelegate::propertyChange, this, &MaterialsEditor::propertyChange);
|
|
}
|
|
|
|
void MaterialsEditor::addRecents(QStandardItem* parent)
|
|
{
|
|
auto tree = ui->treeMaterials;
|
|
for (auto& uuid : _recents) {
|
|
try {
|
|
auto material = getMaterialManager().getMaterial(uuid);
|
|
|
|
QIcon icon = QIcon(material->getLibrary()->getIconPath());
|
|
auto card = new QStandardItem(icon, libraryPath(material));
|
|
card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled
|
|
| Qt::ItemIsDropEnabled);
|
|
card->setData(QVariant(uuid), Qt::UserRole);
|
|
|
|
addExpanded(tree, parent, card);
|
|
}
|
|
catch (const Materials::MaterialNotFound&) {
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::addFavorites(QStandardItem* parent)
|
|
{
|
|
auto tree = ui->treeMaterials;
|
|
for (auto& uuid : _favorites) {
|
|
try {
|
|
auto material = getMaterialManager().getMaterial(uuid);
|
|
|
|
QIcon icon = QIcon(material->getLibrary()->getIconPath());
|
|
auto card = new QStandardItem(icon, libraryPath(material));
|
|
card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled
|
|
| Qt::ItemIsDropEnabled);
|
|
card->setData(QVariant(uuid), Qt::UserRole);
|
|
|
|
addExpanded(tree, parent, card);
|
|
}
|
|
catch (const Materials::MaterialNotFound&) {
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::fillMaterialTree()
|
|
{
|
|
auto param = App::GetApplication().GetParameterGroupByPath(
|
|
"User parameter:BaseApp/Preferences/Mod/Material/Editor/MaterialTree");
|
|
|
|
auto tree = ui->treeMaterials;
|
|
auto model = static_cast<QStandardItemModel*>(tree->model());
|
|
|
|
auto lib = new QStandardItem(tr("Favorites"));
|
|
lib->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);
|
|
addExpanded(tree, model, lib, param);
|
|
addFavorites(lib);
|
|
|
|
lib = new QStandardItem(tr("Recent"));
|
|
lib->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);
|
|
addExpanded(tree, model, lib, param);
|
|
addRecents(lib);
|
|
|
|
auto libraries = getMaterialManager().getMaterialLibraries();
|
|
for (const auto& library : *libraries) {
|
|
lib = new QStandardItem(library->getName());
|
|
lib->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);
|
|
addExpanded(tree, model, lib, param);
|
|
|
|
QIcon icon(library->getIconPath());
|
|
QIcon folderIcon(QString::fromStdString(":/icons/folder.svg"));
|
|
|
|
auto modelTree = getMaterialManager().getMaterialTree(library);
|
|
addMaterials(*lib, modelTree, folderIcon, icon, param);
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::createMaterialTree()
|
|
{
|
|
auto tree = ui->treeMaterials;
|
|
auto model = new QStandardItemModel();
|
|
tree->setModel(model);
|
|
|
|
tree->setHeaderHidden(true);
|
|
fillMaterialTree();
|
|
}
|
|
|
|
void MaterialsEditor::refreshMaterialTree()
|
|
{
|
|
auto tree = ui->treeMaterials;
|
|
auto model = static_cast<QStandardItemModel*>(tree->model());
|
|
model->clear();
|
|
|
|
fillMaterialTree();
|
|
}
|
|
|
|
void MaterialsEditor::updatePreview() const
|
|
{
|
|
if (_material->hasAppearanceProperty(QString::fromStdString("AmbientColor"))) {
|
|
QString color = _material->getAppearanceValueString(QString::fromStdString("AmbientColor"));
|
|
_rendered->setAmbientColor(getColorHash(color, 255));
|
|
}
|
|
else {
|
|
_rendered->resetAmbientColor();
|
|
}
|
|
if (_material->hasAppearanceProperty(QString::fromStdString("DiffuseColor"))) {
|
|
QString color = _material->getAppearanceValueString(QString::fromStdString("DiffuseColor"));
|
|
_rendered->setDiffuseColor(getColorHash(color, 255));
|
|
}
|
|
else {
|
|
_rendered->resetDiffuseColor();
|
|
}
|
|
if (_material->hasAppearanceProperty(QString::fromStdString("SpecularColor"))) {
|
|
QString color =
|
|
_material->getAppearanceValueString(QString::fromStdString("SpecularColor"));
|
|
_rendered->setSpecularColor(getColorHash(color, 255));
|
|
}
|
|
else {
|
|
_rendered->resetSpecularColor();
|
|
}
|
|
if (_material->hasAppearanceProperty(QString::fromStdString("EmissiveColor"))) {
|
|
QString color =
|
|
_material->getAppearanceValueString(QString::fromStdString("EmissiveColor"));
|
|
_rendered->setEmissiveColor(getColorHash(color, 255));
|
|
}
|
|
else {
|
|
_rendered->resetEmissiveColor();
|
|
}
|
|
if (_material->hasAppearanceProperty(QString::fromStdString("Shininess"))) {
|
|
double value =
|
|
_material->getAppearanceValue(QString::fromStdString("Shininess")).toDouble();
|
|
_rendered->setShininess(value);
|
|
}
|
|
else {
|
|
_rendered->resetShininess();
|
|
}
|
|
if (_material->hasAppearanceProperty(QString::fromStdString("Transparency"))) {
|
|
double value =
|
|
_material->getAppearanceValue(QString::fromStdString("Transparency")).toDouble();
|
|
_rendered->setTransparency(value);
|
|
}
|
|
else {
|
|
_rendered->resetTransparency();
|
|
}
|
|
}
|
|
|
|
QString MaterialsEditor::getColorHash(const QString& colorString, int colorRange)
|
|
{
|
|
/*
|
|
returns a '#000000' string from a '(0.1,0.2,0.3)' string. Optionally the string
|
|
has a fourth value for alpha (transparency)
|
|
*/
|
|
std::stringstream stream(colorString.toStdString());
|
|
|
|
char c;
|
|
stream >> c; // read "("
|
|
double red;
|
|
stream >> red;
|
|
stream >> c; // ","
|
|
double green;
|
|
stream >> green;
|
|
stream >> c; // ","
|
|
double blue;
|
|
stream >> blue;
|
|
stream >> c; // ","
|
|
double alpha = 1.0;
|
|
if (c == ',') {
|
|
stream >> alpha;
|
|
}
|
|
|
|
QColor color(static_cast<int>(red * colorRange),
|
|
static_cast<int>(green * colorRange),
|
|
static_cast<int>(blue * colorRange),
|
|
static_cast<int>(alpha * colorRange));
|
|
return color.name();
|
|
}
|
|
|
|
void MaterialsEditor::updateMaterialAppearance()
|
|
{
|
|
QTreeView* tree = ui->treeAppearance;
|
|
auto treeModel = static_cast<QStandardItemModel*>(tree->model());
|
|
treeModel->clear();
|
|
|
|
QStringList headers;
|
|
headers.append(tr("Property"));
|
|
headers.append(tr("Value"));
|
|
headers.append(tr("Type"));
|
|
treeModel->setHorizontalHeaderLabels(headers);
|
|
|
|
tree->setColumnWidth(0, 250);
|
|
tree->setColumnWidth(1, 250);
|
|
tree->setColumnHidden(2, true);
|
|
|
|
auto models = _material->getAppearanceModels();
|
|
if (models) {
|
|
for (auto it = models->begin(); it != models->end(); it++) {
|
|
QString uuid = *it;
|
|
try {
|
|
auto model = getModelManager().getModel(uuid);
|
|
QString name = model->getName();
|
|
|
|
auto modelRoot = new QStandardItem(name);
|
|
modelRoot->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled
|
|
| Qt::ItemIsDropEnabled);
|
|
addExpanded(tree, treeModel, modelRoot);
|
|
for (auto itp = model->begin(); itp != model->end(); itp++) {
|
|
QList<QStandardItem*> items;
|
|
|
|
QString key = itp->first;
|
|
auto propertyItem = new QStandardItem(key);
|
|
propertyItem->setToolTip(itp->second.getDescription());
|
|
items.append(propertyItem);
|
|
|
|
auto valueItem = new QStandardItem(_material->getAppearanceValueString(key));
|
|
valueItem->setToolTip(itp->second.getDescription());
|
|
QVariant variant;
|
|
variant.setValue(_material);
|
|
valueItem->setData(variant);
|
|
items.append(valueItem);
|
|
|
|
auto typeItem = new QStandardItem(itp->second.getPropertyType());
|
|
items.append(typeItem);
|
|
|
|
auto unitsItem = new QStandardItem(itp->second.getUnits());
|
|
items.append(unitsItem);
|
|
|
|
modelRoot->appendRow(items);
|
|
tree->setExpanded(modelRoot->index(), true);
|
|
}
|
|
}
|
|
catch (Materials::ModelNotFound const&) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void MaterialsEditor::updateMaterialProperties()
|
|
{
|
|
QTreeView* tree = ui->treePhysicalProperties;
|
|
auto treeModel = dynamic_cast<QStandardItemModel*>(tree->model());
|
|
treeModel->clear();
|
|
|
|
QStringList headers;
|
|
headers.append(tr("Property"));
|
|
headers.append(tr("Value"));
|
|
headers.append(tr("Type"));
|
|
headers.append(tr("Units"));
|
|
treeModel->setHorizontalHeaderLabels(headers);
|
|
|
|
tree->setColumnWidth(0, 250);
|
|
tree->setColumnWidth(1, 250);
|
|
tree->setColumnHidden(2, true);
|
|
tree->setColumnHidden(3, true);
|
|
|
|
auto models = _material->getPhysicalModels();
|
|
if (models) {
|
|
for (auto it = models->begin(); it != models->end(); it++) {
|
|
QString uuid = *it;
|
|
try {
|
|
auto model = getModelManager().getModel(uuid);
|
|
QString name = model->getName();
|
|
|
|
auto modelRoot = new QStandardItem(name);
|
|
modelRoot->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled
|
|
| Qt::ItemIsDropEnabled);
|
|
addExpanded(tree, treeModel, modelRoot);
|
|
for (auto itp = model->begin(); itp != model->end(); itp++) {
|
|
QList<QStandardItem*> items;
|
|
|
|
QString key = itp->first;
|
|
Materials::ModelProperty modelProperty =
|
|
static_cast<Materials::ModelProperty>(itp->second);
|
|
auto propertyItem = new QStandardItem(key);
|
|
propertyItem->setToolTip(modelProperty.getDescription());
|
|
items.append(propertyItem);
|
|
|
|
auto valueItem = new QStandardItem(_material->getPhysicalValueString(key));
|
|
valueItem->setToolTip(modelProperty.getDescription());
|
|
QVariant variant;
|
|
variant.setValue(_material);
|
|
valueItem->setData(variant);
|
|
items.append(valueItem);
|
|
|
|
auto typeItem = new QStandardItem(modelProperty.getPropertyType());
|
|
items.append(typeItem);
|
|
|
|
auto unitsItem = new QStandardItem(modelProperty.getUnits());
|
|
items.append(unitsItem);
|
|
|
|
// addExpanded(tree, modelRoot, propertyItem);
|
|
modelRoot->appendRow(items);
|
|
tree->setExpanded(modelRoot->index(), true);
|
|
}
|
|
}
|
|
catch (Materials::ModelNotFound const&) {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
QString MaterialsEditor::libraryPath(const std::shared_ptr<Materials::Material>& material)
|
|
{
|
|
QString path;
|
|
auto library = material->getLibrary();
|
|
if (library) {
|
|
path = QString::fromStdString("/%1/%2")
|
|
.arg(material->getLibrary()->getName())
|
|
.arg(material->getDirectory());
|
|
}
|
|
else {
|
|
path = QString::fromStdString("%1").arg(material->getDirectory());
|
|
}
|
|
|
|
return path;
|
|
}
|
|
|
|
void MaterialsEditor::updateMaterialGeneral()
|
|
{
|
|
QString parentString;
|
|
try {
|
|
auto parent = _materialManager.getParent(_material);
|
|
parentString = libraryPath(parent);
|
|
}
|
|
catch (const Materials::MaterialNotFound&) {
|
|
}
|
|
|
|
// Update the general information
|
|
ui->editName->setText(_material->getName());
|
|
ui->editAuthor->setText(_material->getAuthor());
|
|
ui->editLicense->setText(_material->getLicense());
|
|
ui->editParent->setText(parentString);
|
|
ui->editParent->setReadOnly(true);
|
|
ui->editSourceURL->setText(_material->getURL());
|
|
ui->editSourceReference->setText(_material->getReference());
|
|
// ui->editTags->setText(_material->getName());
|
|
ui->editDescription->setText(_material->getDescription());
|
|
}
|
|
|
|
void MaterialsEditor::updateMaterial()
|
|
{
|
|
updateMaterialGeneral();
|
|
updateMaterialProperties();
|
|
updateMaterialAppearance();
|
|
|
|
updatePreview();
|
|
}
|
|
|
|
void MaterialsEditor::onSelectMaterial(const QItemSelection& selected,
|
|
const QItemSelection& deselected)
|
|
{
|
|
Q_UNUSED(deselected);
|
|
|
|
// Get the UUID before changing the underlying data model
|
|
QString uuid;
|
|
auto model = static_cast<QStandardItemModel*>(ui->treeMaterials->model());
|
|
QModelIndexList indexes = selected.indexes();
|
|
for (auto it = indexes.begin(); it != indexes.end(); it++) {
|
|
QStandardItem* item = model->itemFromIndex(*it);
|
|
|
|
if (item) {
|
|
uuid = item->data(Qt::UserRole).toString();
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (uuid.isEmpty() || uuid == _material->getUUID()) {
|
|
return;
|
|
}
|
|
|
|
// Ensure data is saved (or discarded) before changing materials
|
|
if (_material->getEditState() != Materials::Material::ModelEdit_None) {
|
|
// Prompt the user to save or discard changes
|
|
int res = confirmSave(this);
|
|
if (res == QMessageBox::Cancel) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get the selected material
|
|
try {
|
|
_material = std::make_shared<Materials::Material>(*getMaterialManager().getMaterial(uuid));
|
|
}
|
|
catch (Materials::ModelNotFound const&) {
|
|
Base::Console().Log("*** Unable to load material '%s'\n", uuid.toStdString().c_str());
|
|
_material = std::make_shared<Materials::Material>();
|
|
}
|
|
|
|
updateMaterial();
|
|
_material->resetEditState();
|
|
}
|
|
|
|
void MaterialsEditor::onDoubleClick(const QModelIndex& index)
|
|
{
|
|
Q_UNUSED(index)
|
|
|
|
accept();
|
|
}
|
|
|
|
void MaterialsEditor::onContextMenu(const QPoint& pos)
|
|
{
|
|
QMenu contextMenu(tr("Context menu"), this);
|
|
|
|
QAction action1(tr("Inherit from"), this);
|
|
// action1.setShortcut(Qt::Key_Delete);
|
|
connect(&action1, &QAction::triggered, this, &MaterialsEditor::onInherit);
|
|
contextMenu.addAction(&action1);
|
|
|
|
QAction action2(tr("Inherit new material"), this);
|
|
// action1.setShortcut(Qt::Key_Delete);
|
|
connect(&action2, &QAction::triggered, this, &MaterialsEditor::onInheritNew);
|
|
contextMenu.addAction(&action2);
|
|
|
|
contextMenu.exec(ui->treeMaterials->mapToGlobal(pos));
|
|
}
|
|
|
|
void MaterialsEditor::onInherit(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
}
|
|
|
|
void MaterialsEditor::onInheritNew(bool checked)
|
|
{
|
|
Q_UNUSED(checked)
|
|
}
|
|
|
|
int MaterialsEditor::confirmSave(QWidget* parent)
|
|
{
|
|
QMessageBox box(parent ? parent : this);
|
|
box.setIcon(QMessageBox::Question);
|
|
box.setWindowTitle(QObject::tr("Unsaved Material"));
|
|
box.setText(QObject::tr("Do you want to save your changes to the material before closing?"));
|
|
|
|
box.setInformativeText(QObject::tr("If you don't save, your changes will be lost."));
|
|
box.setStandardButtons(QMessageBox::Discard | QMessageBox::Cancel | QMessageBox::Save);
|
|
box.setDefaultButton(QMessageBox::Save);
|
|
box.setEscapeButton(QMessageBox::Cancel);
|
|
|
|
// add shortcuts
|
|
QAbstractButton* saveBtn = box.button(QMessageBox::Save);
|
|
if (saveBtn->shortcut().isEmpty()) {
|
|
QString text = saveBtn->text();
|
|
text.prepend(QLatin1Char('&'));
|
|
saveBtn->setShortcut(QKeySequence::mnemonic(text));
|
|
}
|
|
|
|
QAbstractButton* discardBtn = box.button(QMessageBox::Discard);
|
|
if (discardBtn->shortcut().isEmpty()) {
|
|
QString text = discardBtn->text();
|
|
text.prepend(QLatin1Char('&'));
|
|
discardBtn->setShortcut(QKeySequence::mnemonic(text));
|
|
}
|
|
|
|
int res = QMessageBox::Cancel;
|
|
box.adjustSize(); // Silence warnings from Qt on Windows
|
|
switch (box.exec()) {
|
|
case QMessageBox::Save:
|
|
saveMaterial();
|
|
res = QMessageBox::Save;
|
|
break;
|
|
case QMessageBox::Discard:
|
|
res = QMessageBox::Discard;
|
|
break;
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
#include "moc_MaterialsEditor.cpp"
|