1234 lines
38 KiB
C++
1234 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>
|
|
#include <limits>
|
|
#endif
|
|
|
|
#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; static_cast<long>(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; static_cast<long>(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; static_cast<long>(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; static_cast<long>(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();
|
|
}
|
|
update();
|
|
_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)
|
|
|
|
// Ensure data is saved (or discarded) before exiting
|
|
if (_material->getEditState() != Materials::Material::ModelEdit_None) {
|
|
// Prompt the user to save or discard changes
|
|
int res = confirmSave(this);
|
|
if (res == QMessageBox::Cancel) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
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 = dynamic_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 = dynamic_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 = dynamic_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 = dynamic_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 = dynamic_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)
|
|
|
|
// Ensure data is saved (or discarded) before exiting
|
|
if (_material->getEditState() != Materials::Material::ModelEdit_None) {
|
|
// Prompt the user to save or discard changes
|
|
int res = confirmSave(this);
|
|
if (res == QMessageBox::Cancel) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
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"
|