Material: Material editor enhancements

Continues the work of the material subsystem improvements.

This merge covers the continued development of the material editor. The
primary improvements are in the handling of 2D and 3D array properties.
These properties are now fully editable, and can be saved and restored.

The cards now separate the author and license. These were previously
saved as a single item. Future support will be provided for standard
open source licenses.

Saving operations validate the cards to ensure UUIDs of materials are
considered. Warnings are given when a save could potentially impact the
models, such as saving over a material instead of creating a new
instance.

The editor is still not complete. There are a number of functional
elements, such as drag/drop operations, folder creation, and deletion
operations that need to be added to the main tree. State needs to be
saved and restored to improve the user experience. The appearance
preview also needs significant work. This will be handled in a future
PR.
This commit is contained in:
David Carter
2023-10-23 15:19:20 -04:00
parent aaa962944b
commit 58bacb6b40
221 changed files with 6493 additions and 1614 deletions

View File

@@ -23,7 +23,11 @@
#ifndef _PreComp_
#endif
#include <QMetaType>
#include <App/Application.h>
#include <Base/Quantity.h>
#include <Gui/MetaTypes.h>
#include "Exceptions.h"
#include "MaterialValue.h"
@@ -33,29 +37,203 @@ using namespace Materials;
/* TRANSLATOR Material::MaterialValue */
TYPESYSTEM_SOURCE(Materials::MaterialValue, Base::BaseClass)
MaterialValue::MaterialValue()
: _valueType(None)
{
this->setInitialValue(None);
}
MaterialValue::MaterialValue(const MaterialValue& other)
: _valueType(other._valueType)
, _value(other._value)
{}
MaterialValue::MaterialValue(ValueType type)
: _valueType(type)
{}
{
this->setInitialValue(None);
}
MaterialValue::MaterialValue(ValueType type, ValueType inherited)
: _valueType(type)
{
this->setInitialValue(inherited);
}
MaterialValue& MaterialValue::operator=(const MaterialValue& other)
{
if (this == &other) {
return *this;
}
_valueType = other._valueType;
_value = other._value;
return *this;
}
bool MaterialValue::operator==(const MaterialValue& other) const
{
if (this == &other) {
return true;
}
return (_valueType == other._valueType) && (_value == other._value);
}
void MaterialValue::setInitialValue(ValueType inherited)
{
if (_valueType == String) {
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
}
else if (_valueType == Boolean) {
_value = QVariant(static_cast<QVariant::Type>(QMetaType::Bool));
}
else if (_valueType == Integer) {
_value = QVariant(static_cast<QVariant::Type>(QMetaType::Int));
}
else if (_valueType == Float) {
_value = QVariant(static_cast<QVariant::Type>(QMetaType::Float));
}
else if (_valueType == URL) {
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
}
else if (_valueType == Quantity) {
Base::Quantity q;
q.setInvalid();
_value = QVariant::fromValue(q);
}
else if (_valueType == Color) {
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
}
else if (_valueType == File) {
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
}
else if (_valueType == Image) {
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
}
else if (_valueType == List) {
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
}
else if (_valueType == Array2D) {
if (_valueType != inherited) {
throw InvalidMaterialType("Initializing a regular material value as a 2D Array");
}
_value = QVariant(); // Uninitialized default value
}
else if (_valueType == Array3D) {
if (_valueType != inherited) {
throw InvalidMaterialType("Initializing a regular material value as a 3D Array");
}
_value = QVariant(); // Uninitialized default value
}
else {
// Default is to set the type to None and leave the variant uninitialized
_valueType = None;
_value = QVariant();
}
}
bool MaterialValue::isNull() const
{
if (_value.isNull()) {
return true;
}
if (_valueType == Quantity) {
return !_value.value<Base::Quantity>().isValid();
}
return false;
}
const QString MaterialValue::getYAMLString() const
{
QString yaml = QString::fromStdString("\"");
if (!isNull()) {
if (getType() == MaterialValue::Quantity) {
Base::Quantity quantity = getValue().value<Base::Quantity>();
yaml += quantity.getUserString();
}
else if (getType() == MaterialValue::Float) {
auto value = getValue();
if (!value.isNull()) {
yaml += QString(QString::fromStdString("%1")).arg(value.toFloat(), 0, 'g', 6);
}
}
else {
yaml += getValue().toString();
}
}
yaml += QString::fromStdString("\"");
return yaml;
}
//===
TYPESYSTEM_SOURCE(Materials::Material2DArray, Materials::MaterialValue)
Material2DArray::Material2DArray()
: MaterialValue(Array2D)
: MaterialValue(Array2D, Array2D)
, _defaultSet(false)
{}
MaterialValue Material2DArray::getDefault() const
{
MaterialValue ret(_valueType);
ret.setValue(_value);
return ret;
// Initialize separatelt to prevent recursion
// setType(Array2D);
}
const std::vector<QVariant>* Material2DArray::getRow(int row) const
Material2DArray::Material2DArray(const Material2DArray& other)
: MaterialValue(other)
, _defaultSet(other._defaultSet)
{
deepCopy(other);
}
Material2DArray& Material2DArray::operator=(const Material2DArray& other)
{
if (this == &other) {
return *this;
}
MaterialValue::operator=(other);
_defaultSet = other._defaultSet;
deepCopy(other);
return *this;
}
void Material2DArray::deepCopy(const Material2DArray& other)
{
// Deep copy
for (auto row : other._rows) {
std::vector<QVariant> v;
for (auto col : *row) {
QVariant newVariant(col);
v.push_back(newVariant);
}
addRow(std::make_shared<std::vector<QVariant>>(v));
}
}
bool Material2DArray::isNull() const
{
return rows() <= 0;
}
const QVariant Material2DArray::getDefault() const
{
if (_defaultSet) {
return _value;
}
return QVariant();
}
std::shared_ptr<std::vector<QVariant>> Material2DArray::getRow(int row) const
{
try {
return _rows.at(row);
@@ -65,7 +243,7 @@ const std::vector<QVariant>* Material2DArray::getRow(int row) const
}
}
std::vector<QVariant>* Material2DArray::getRow(int row)
std::shared_ptr<std::vector<QVariant>> Material2DArray::getRow(int row)
{
try {
return _rows.at(row);
@@ -75,12 +253,12 @@ std::vector<QVariant>* Material2DArray::getRow(int row)
}
}
void Material2DArray::addRow(std::vector<QVariant>* row)
void Material2DArray::addRow(std::shared_ptr<std::vector<QVariant>> row)
{
_rows.push_back(row);
}
void Material2DArray::insertRow(int index, std::vector<QVariant>* row)
void Material2DArray::insertRow(int index, std::shared_ptr<std::vector<QVariant>> row)
{
_rows.insert(_rows.begin() + index, row);
}
@@ -99,7 +277,7 @@ void Material2DArray::setValue(int row, int column, const QVariant& value)
throw InvalidIndex();
}
std::vector<QVariant>* val = getRow(row);
auto val = getRow(row);
try {
val->at(column) = value;
}
@@ -124,10 +302,10 @@ const QVariant Material2DArray::getValue(int row, int column) const
}
}
void Material2DArray::dumpRow(const std::vector<QVariant>& row) const
void Material2DArray::dumpRow(std::shared_ptr<std::vector<QVariant>> row) const
{
Base::Console().Log("row: ");
for (auto column : row) {
for (auto column : *row) {
Base::Console().Log("'%s' ", column.toString().toStdString().c_str());
}
Base::Console().Log("\n");
@@ -136,107 +314,389 @@ void Material2DArray::dumpRow(const std::vector<QVariant>& row) const
void Material2DArray::dump() const
{
for (auto row : _rows) {
dumpRow(*row);
dumpRow(row);
}
}
const QString Material2DArray::getYAMLString() const
{
if (isNull()) {
return QString();
}
// Set the correct indentation. 9 chars in this case
QString pad;
pad.fill(QChar::fromLatin1(' '), 9);
// Save the default value
QString yaml = QString::fromStdString("\n - \"");
Base::Quantity quantity = getDefault().value<Base::Quantity>();
yaml += quantity.getUserString();
yaml += QString::fromStdString("\"\n");
// Next the array contents
yaml += QString::fromStdString(" - [");
bool firstRow = true;
for (auto row : _rows) {
if (!firstRow) {
// Each row is on its own line, padded for correct indentation
yaml += QString::fromStdString(",\n") + pad;
}
else {
firstRow = false;
}
yaml += QString::fromStdString("[");
bool first = true;
for (auto column : *row) {
if (!first) {
// TODO: Fix for arrays with too many columns to fit on a single line
yaml += QString::fromStdString(", ");
}
else {
first = false;
}
yaml += QString::fromStdString("\"");
Base::Quantity quantity = column.value<Base::Quantity>();
yaml += quantity.getUserString();
yaml += QString::fromStdString("\"");
}
yaml += QString::fromStdString("]");
}
yaml += QString::fromStdString("]");
return yaml;
}
//===
TYPESYSTEM_SOURCE(Materials::Material3DArray, Materials::MaterialValue)
Material3DArray::Material3DArray()
: MaterialValue(Array3D)
: MaterialValue(Array3D, Array3D)
, _defaultSet(false)
{}
MaterialValue Material3DArray::getDefault() const
, _currentDepth(0)
{
MaterialValue ret(_valueType);
ret.setValue(_value);
return ret;
// Initialize separatelt to prevent recursion
// setType(Array3D);
}
const std::vector<std::vector<QVariant>*>& Material3DArray::getTable(const QVariant& depth) const
bool Material3DArray::isNull() const
{
return depth() <= 0;
}
const QVariant Material3DArray::getDefault() const
{
return _value;
}
const std::shared_ptr<std::vector<std::shared_ptr<std::vector<Base::Quantity>>>>&
Material3DArray::getTable(const Base::Quantity& depth) const
{
for (auto it = _rowMap.begin(); it != _rowMap.end(); it++) {
if (std::get<0>(*it) == depth) {
return std::get<1>(*it);
}
}
throw InvalidDepth();
}
const std::shared_ptr<std::vector<std::shared_ptr<std::vector<Base::Quantity>>>>&
Material3DArray::getTable(int depthIndex) const
{
try {
return _rowMap.at(depth);
return std::get<1>(_rowMap.at(depthIndex));
}
catch (std::out_of_range const&) {
throw InvalidDepth();
}
}
const std::shared_ptr<std::vector<Base::Quantity>> Material3DArray::getRow(int depth, int row) const
{
try {
return getTable(depth)->at(row);
}
catch (std::out_of_range const&) {
throw InvalidRow();
}
}
const std::vector<QVariant>& Material3DArray::getRow(const QVariant& depth, int row) const
std::shared_ptr<std::vector<Base::Quantity>> Material3DArray::getRow(int row) const
{
// Check if we can convert otherwise throw error
return getRow(_currentDepth, row);
}
std::shared_ptr<std::vector<Base::Quantity>> Material3DArray::getRow(int depth, int row)
{
try {
return *(_rowMap.at(depth).at(row));
return getTable(depth)->at(row);
}
catch (std::out_of_range const&) {
throw InvalidRow();
}
}
const std::vector<QVariant>& Material3DArray::getRow(int row) const
std::shared_ptr<std::vector<Base::Quantity>> Material3DArray::getRow(int row)
{
return getRow(getDefault().getValue().toString(), row);
return getRow(_currentDepth, row);
}
std::vector<QVariant>& Material3DArray::getRow(const QVariant& depth, int row)
void Material3DArray::addRow(int depth, std::shared_ptr<std::vector<Base::Quantity>> row)
{
try {
return *(_rowMap.at(depth).at(row));
getTable(depth)->push_back(row);
}
catch (std::out_of_range const&) {
throw InvalidRow();
}
}
std::vector<QVariant>& Material3DArray::getRow(int row)
void Material3DArray::addRow(std::shared_ptr<std::vector<Base::Quantity>> row)
{
return getRow(getDefault().getValue().toString(), row);
addRow(_currentDepth, row);
}
void Material3DArray::addRow(const QVariant& depth, std::vector<QVariant>* row)
int Material3DArray::addDepth(int depth, Base::Quantity value)
{
_rowMap[depth].push_back(row);
if (depth == this->depth()) {
// Append to the end
return addDepth(value);
}
else if (depth > this->depth()) {
throw InvalidDepth();
}
auto rowVector = std::make_shared<std::vector<std::shared_ptr<std::vector<Base::Quantity>>>>();
auto entry = std::make_pair(value, rowVector);
_rowMap.insert(_rowMap.begin() + depth, entry);
return depth;
}
void Material3DArray::deleteRow(const QVariant& depth, int row)
int Material3DArray::addDepth(Base::Quantity value)
{
Q_UNUSED(depth)
Q_UNUSED(row)
auto rowVector = std::make_shared<std::vector<std::shared_ptr<std::vector<Base::Quantity>>>>();
auto entry = std::make_pair(value, rowVector);
_rowMap.push_back(entry);
return depth() - 1;
}
void Material3DArray::deleteDepth(int depth)
{
deleteRows(depth); // This may throw an InvalidDepth
_rowMap.erase(_rowMap.begin() + depth);
}
void Material3DArray::insertRow(int depth,
int row,
std::shared_ptr<std::vector<Base::Quantity>> rowData)
{
try {
auto table = getTable(depth);
// auto it = table->begin();
// std::advance(it, row);
table->insert(table->begin() + row, rowData);
}
catch (std::out_of_range const&) {
throw InvalidRow();
}
}
void Material3DArray::insertRow(int row, std::shared_ptr<std::vector<Base::Quantity>> rowData)
{
insertRow(_currentDepth, row, rowData);
}
void Material3DArray::deleteRow(int depth, int row)
{
auto table = getTable(depth);
if (static_cast<std::size_t>(row) >= table->size() || row < 0) {
throw InvalidRow();
}
table->erase(table->begin() + row);
}
void Material3DArray::deleteRow(int row)
{
deleteRow(_currentDepth, row);
}
void Material3DArray::deleteRows(int depth)
{
Q_UNUSED(depth)
auto table = getTable(depth);
table->clear();
}
void Material3DArray::setValue(const QVariant& depth, int row, int column, const QVariant& value)
void Material3DArray::deleteRows()
{
Q_UNUSED(depth)
Q_UNUSED(row)
Q_UNUSED(column)
Q_UNUSED(value)
deleteRows(_currentDepth);
}
void Material3DArray::setValue(int row, int column, const QVariant& value)
int Material3DArray::rows(int depth) const
{
Q_UNUSED(row)
Q_UNUSED(column)
Q_UNUSED(value)
if (depth < 0 || (depth == 0 && this->depth() == 0)) {
return 0;
}
return getTable(depth)->size();
}
const QVariant Material3DArray::getValue(const QVariant& depth, int row, int column)
int Material3DArray::columns(int depth) const
{
if (depth < 0 || (depth == 0 && this->depth() == 0)) {
return 0;
}
if (rows() == 0) {
return 0;
}
return getTable(depth)->at(0)->size();
}
void Material3DArray::setValue(int depth, int row, int column, const Base::Quantity& value)
{
auto val = getRow(depth, row);
try {
return val.at(column);
val->at(column) = value;
}
catch (std::out_of_range const&) {
throw InvalidColumn();
}
}
const QVariant Material3DArray::getValue(int row, int column)
void Material3DArray::setValue(int row, int column, const Base::Quantity& value)
{
return getValue(getDefault().getValue().toString(), row, column);
setValue(_currentDepth, row, column, value);
}
void Material3DArray::setDepthValue(int depth, const Base::Quantity& value)
{
try {
auto oldRows = getTable(depth);
_rowMap.at(depth) = std::pair(value, oldRows);
}
catch (std::out_of_range const&) {
throw InvalidRow();
}
}
void Material3DArray::setDepthValue(const Base::Quantity& value)
{
setDepthValue(_currentDepth, value);
}
const Base::Quantity Material3DArray::getValue(int depth, int row, int column) const
{
auto val = getRow(depth, row);
try {
return val->at(column);
}
catch (std::out_of_range const&) {
throw InvalidColumn();
}
}
const Base::Quantity Material3DArray::getValue(int row, int column) const
{
return getValue(_currentDepth, row, column);
}
const Base::Quantity Material3DArray::getDepthValue(int depth) const
{
try {
return std::get<0>(_rowMap.at(depth));
}
catch (std::out_of_range const&) {
throw InvalidRow();
}
}
int Material3DArray::currentDepth() const
{
return _currentDepth;
}
void Material3DArray::setCurrentDepth(int depth)
{
if (depth < 0 || _rowMap.size() == 0) {
_currentDepth = 0;
}
else if (static_cast<std::size_t>(depth) >= _rowMap.size()) {
_currentDepth = _rowMap.size() - 1;
}
else {
_currentDepth = depth;
}
}
const QString Material3DArray::getYAMLString() const
{
if (isNull()) {
return QString();
}
// Set the correct indentation. 7 chars + name length
QString pad;
pad.fill(QChar::fromLatin1(' '), 9);
// Save the default value
QString yaml = QString::fromStdString("\n - \"");
Base::Quantity quantity = getDefault().value<Base::Quantity>();
yaml += quantity.getUserString();
yaml += QString::fromStdString("\"\n");
// Next the array contents
yaml += QString::fromStdString(" - [");
for (int depth = 0; depth < this->depth(); depth++) {
if (depth > 0) {
// Each row is on its own line, padded for correct indentation
yaml += QString::fromStdString(",\n") + pad;
}
yaml += QString::fromStdString("\"");
auto value = getDepthValue(depth).getUserString();
yaml += value;
yaml += QString::fromStdString("\": [");
QString pad2;
pad2.fill(QChar::fromLatin1(' '), 14 + value.length());
bool firstRow = true;
auto rows = getTable(depth);
for (auto row : *rows) {
if (!firstRow) {
// Each row is on its own line, padded for correct indentation
yaml += QString::fromStdString(",\n") + pad2;
}
else {
firstRow = false;
}
yaml += QString::fromStdString("[");
bool first = true;
for (auto column : *row) {
if (!first) {
// TODO: Fix for arrays with too many columns to fit on a single line
yaml += QString::fromStdString(", ");
}
else {
first = false;
}
yaml += QString::fromStdString("\"");
// Base::Quantity quantity = column.value<Base::Quantity>();
yaml += column.getUserString();
yaml += QString::fromStdString("\"");
}
yaml += QString::fromStdString("]");
}
yaml += QString::fromStdString("]");
}
yaml += QString::fromStdString("]");
return yaml;
}