Files
create/src/Mod/Material/App/MaterialValue.cpp
Benjamin Bræstrup Sayoc bfb5dfe5da Material: use QStringLiteral 2
2025-02-16 19:50:34 +01:00

858 lines
22 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 <QMetaType>
#include <QRegularExpression>
#endif
#include <App/Application.h>
#include <Base/QtTools.h>
#include <Base/Quantity.h>
#include <Gui/MetaTypes.h>
#include "Exceptions.h"
#include "MaterialValue.h"
using namespace Materials;
/* TRANSLATOR Material::MaterialValue */
TYPESYSTEM_SOURCE(Materials::MaterialValue, Base::BaseClass)
QMap<QString, MaterialValue::ValueType> MaterialValue::_typeMap {
{QStringLiteral("String"), String},
{QStringLiteral("Boolean"), Boolean},
{QStringLiteral("Integer"), Integer},
{QStringLiteral("Float"), Float},
{QStringLiteral("Quantity"), Quantity},
{QStringLiteral("Distribution"), Distribution},
{QStringLiteral("List"), List},
{QStringLiteral("2DArray"), Array2D},
{QStringLiteral("3DArray"), Array3D},
{QStringLiteral("Color"), Color},
{QStringLiteral("Image"), Image},
{QStringLiteral("File"), File},
{QStringLiteral("URL"), URL},
{QStringLiteral("MultiLineString"), MultiLineString},
{QStringLiteral("FileList"), FileList},
{QStringLiteral("ImageList"), ImageList},
{QStringLiteral("SVG"), SVG}};
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);
}
QString MaterialValue::escapeString(const QString& source)
{
QString res = source;
res.replace(QStringLiteral("\\"), QStringLiteral("\\\\"));
res.replace(QStringLiteral("\""), QStringLiteral("\\\""));
return res;
}
MaterialValue::ValueType MaterialValue::mapType(const QString& stringType)
{
// If not found, return None
return _typeMap.value(stringType, None);
}
void MaterialValue::setInitialValue(ValueType inherited)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if (_valueType == String || _valueType == MultiLineString || _valueType == SVG) {
_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 == 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 == String || _valueType == MultiLineString || _valueType == SVG) {
_value = QVariant(QMetaType(QMetaType::QString));
}
else if (_valueType == Boolean) {
_value = QVariant(QMetaType(QMetaType::Bool));
}
else if (_valueType == Integer) {
_value = QVariant(QMetaType(QMetaType::Int));
}
else if (_valueType == Float) {
_value = QVariant(QMetaType(QMetaType::Float));
}
else if (_valueType == URL) {
_value = QVariant(QMetaType(QMetaType::QString));
}
else if (_valueType == Color) {
_value = QVariant(QMetaType(QMetaType::QString));
}
else if (_valueType == File) {
_value = QVariant(QMetaType(QMetaType::QString));
}
else if (_valueType == Image) {
_value = QVariant(QMetaType(QMetaType::QString));
}
#endif
else if (_valueType == Quantity) {
Base::Quantity qu;
qu.setInvalid();
_value = QVariant::fromValue(qu);
}
else if (_valueType == List || _valueType == FileList || _valueType == ImageList) {
auto list = QList<QVariant>();
_value = QVariant::fromValue(list);
}
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();
}
}
void MaterialValue::setList(const QList<QVariant>& value)
{
_value = QVariant::fromValue(value);
}
bool MaterialValue::isNull() const
{
if (_value.isNull()) {
return true;
}
if (_valueType == Quantity) {
return !_value.value<Base::Quantity>().isValid();
}
if (_valueType == List || _valueType == FileList || _valueType == ImageList) {
return _value.value<QList<QVariant>>().isEmpty();
}
return false;
}
QString MaterialValue::getYAMLStringImage() const
{
QString yaml;
yaml = QStringLiteral(" |-2");
QString base64 = getValue().toString();
while (!base64.isEmpty()) {
yaml += QStringLiteral("\n ") + base64.left(74);
base64.remove(0, 74);
}
return yaml;
}
QString MaterialValue::getYAMLStringList() const
{
QString yaml;
for (auto& it : getList()) {
yaml += QStringLiteral("\n - \"") + escapeString(it.toString())
+ QStringLiteral("\"");
}
return yaml;
}
QString MaterialValue::getYAMLStringImageList() const
{
QString yaml;
for (auto& it : getList()) {
yaml += QStringLiteral("\n - |-2");
QString base64 = it.toString();
while (!base64.isEmpty()) {
yaml += QStringLiteral("\n ") + base64.left(72);
base64.remove(0, 72);
}
}
return yaml;
}
QString MaterialValue::getYAMLStringMultiLine() const
{
QString yaml;
yaml = QStringLiteral(" |2");
auto list =
getValue().toString().split(QRegularExpression(QStringLiteral("[\r\n]")), Qt::SkipEmptyParts);
for (auto& it : list) {
yaml += QStringLiteral("\n ") + it;
}
return yaml;
}
QString MaterialValue::getYAMLString() const
{
QString yaml;
if (!isNull()) {
if (getType() == MaterialValue::Image) {
return getYAMLStringImage();
}
if (getType() == MaterialValue::List || getType() == MaterialValue::FileList) {
return getYAMLStringList();
}
if (getType() == MaterialValue::ImageList) {
return getYAMLStringImageList();
}
if (getType() == MaterialValue::MultiLineString || getType() == MaterialValue::SVG) {
return getYAMLStringMultiLine();
}
if (getType() == MaterialValue::Quantity) {
auto quantity = getValue().value<Base::Quantity>();
yaml += QString::fromStdString(quantity.getUserString());
}
else if (getType() == MaterialValue::Float) {
auto value = getValue();
if (!value.isNull()) {
yaml += QStringLiteral("%1").arg(value.toFloat(), 0, 'g', 6);
}
}
else if (getType() == MaterialValue::List) {
for (auto& it : getList()) {
yaml += QStringLiteral("\n - \"") + escapeString(it.toString())
+ QStringLiteral("\"");
}
return yaml;
}
else {
yaml += getValue().toString();
}
}
yaml = QStringLiteral(" \"") + escapeString(yaml) + QStringLiteral("\"");
return yaml;
}
const Base::QuantityFormat MaterialValue::getQuantityFormat()
{
return Base::QuantityFormat(Base::QuantityFormat::NumberFormat::Default, PRECISION);
}
//===
TYPESYSTEM_SOURCE(Materials::Material2DArray, Materials::MaterialValue)
Material2DArray::Material2DArray()
: MaterialValue(Array2D, Array2D)
, _columns(0)
{
// Initialize separatelt to prevent recursion
// setType(Array2D);
}
Material2DArray::Material2DArray(const Material2DArray& other)
: MaterialValue(other)
, _columns(other._columns)
{
deepCopy(other);
}
Material2DArray& Material2DArray::operator=(const Material2DArray& other)
{
if (this == &other) {
return *this;
}
MaterialValue::operator=(other);
_columns = other._columns;
deepCopy(other);
return *this;
}
void Material2DArray::deepCopy(const Material2DArray& other)
{
// Deep copy
for (auto& row : other._rows) {
QList<QVariant> vv;
for (auto& col : *row) {
QVariant newVariant(col);
vv.push_back(newVariant);
}
addRow(std::make_shared<QList<QVariant>>(vv));
}
}
bool Material2DArray::isNull() const
{
return rows() <= 0;
}
void Material2DArray::validateRow(int row) const
{
if (row < 0 || row >= rows()) {
throw InvalidIndex();
}
}
void Material2DArray::validateColumn(int column) const
{
if (column < 0 || column >= columns()) {
throw InvalidIndex();
}
}
std::shared_ptr<QList<QVariant>> Material2DArray::getRow(int row) const
{
validateRow(row);
try {
return _rows.at(row);
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
std::shared_ptr<QList<QVariant>> Material2DArray::getRow(int row)
{
validateRow(row);
try {
return _rows.at(row);
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
void Material2DArray::addRow(const std::shared_ptr<QList<QVariant>>& row)
{
_rows.push_back(row);
}
void Material2DArray::insertRow(int index, const std::shared_ptr<QList<QVariant>>& row)
{
_rows.insert(_rows.begin() + index, row);
}
void Material2DArray::deleteRow(int row)
{
if (row >= static_cast<int>(_rows.size()) || row < 0) {
throw InvalidIndex();
}
_rows.erase(_rows.begin() + row);
}
void Material2DArray::setValue(int row, int column, const QVariant& value)
{
validateRow(row);
validateColumn(column);
auto val = getRow(row);
try {
val->replace(column, value);
}
catch (const std::out_of_range&) {
throw InvalidIndex();
}
}
QVariant Material2DArray::getValue(int row, int column) const
{
validateColumn(column);
auto val = getRow(row);
try {
return val->at(column);
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
void Material2DArray::dumpRow(const std::shared_ptr<QList<QVariant>>& row)
{
Base::Console().Log("row: ");
for (auto& column : *row) {
Base::Console().Log("'%s' ", column.toString().toStdString().c_str());
}
Base::Console().Log("\n");
}
void Material2DArray::dump() const
{
for (auto& row : _rows) {
dumpRow(row);
}
}
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 array contents
QString yaml = QStringLiteral("\n - [");
bool firstRow = true;
for (auto& row : _rows) {
if (!firstRow) {
// Each row is on its own line, padded for correct indentation
yaml += QStringLiteral(",\n") + pad;
}
else {
firstRow = false;
}
yaml += QStringLiteral("[");
bool first = true;
for (auto& column : *row) {
if (!first) {
// TODO: Fix for arrays with too many columns to fit on a single line
yaml += QStringLiteral(", ");
}
else {
first = false;
}
yaml += QStringLiteral("\"");
auto quantity = column.value<Base::Quantity>();
yaml += QString::fromStdString(quantity.getUserString());
yaml += QStringLiteral("\"");
}
yaml += QStringLiteral("]");
}
yaml += QStringLiteral("]");
return yaml;
}
//===
TYPESYSTEM_SOURCE(Materials::Material3DArray, Materials::MaterialValue)
Material3DArray::Material3DArray()
: MaterialValue(Array3D, Array3D)
, _currentDepth(0)
, _columns(0)
{
// Initialize separatelt to prevent recursion
// setType(Array3D);
}
bool Material3DArray::isNull() const
{
return depth() <= 0;
}
void Material3DArray::validateDepth(int level) const
{
if (level < 0 || level >= depth()) {
throw InvalidIndex();
}
}
void Material3DArray::validateColumn(int column) const
{
if (column < 0 || column >= columns()) {
throw InvalidIndex();
}
}
void Material3DArray::validateRow(int level, int row) const
{
validateDepth(level);
if (row < 0 || row >= rows(level)) {
throw InvalidIndex();
}
}
const std::shared_ptr<QList<std::shared_ptr<QList<Base::Quantity>>>>&
Material3DArray::getTable(const Base::Quantity& depth) const
{
for (auto& it : _rowMap) {
if (std::get<0>(it) == depth) {
return std::get<1>(it);
}
}
throw InvalidIndex();
}
const std::shared_ptr<QList<std::shared_ptr<QList<Base::Quantity>>>>&
Material3DArray::getTable(int depthIndex) const
{
try {
return std::get<1>(_rowMap.at(depthIndex));
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
std::shared_ptr<QList<Base::Quantity>> Material3DArray::getRow(int depth, int row) const
{
validateRow(depth, row);
try {
return getTable(depth)->at(row);
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
std::shared_ptr<QList<Base::Quantity>> Material3DArray::getRow(int row) const
{
// Check if we can convert otherwise throw error
return getRow(_currentDepth, row);
}
std::shared_ptr<QList<Base::Quantity>> Material3DArray::getRow(int depth, int row)
{
validateRow(depth, row);
try {
return getTable(depth)->at(row);
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
std::shared_ptr<QList<Base::Quantity>> Material3DArray::getRow(int row)
{
return getRow(_currentDepth, row);
}
void Material3DArray::addRow(int depth, const std::shared_ptr<QList<Base::Quantity>>& row)
{
try {
getTable(depth)->push_back(row);
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
void Material3DArray::addRow(const std::shared_ptr<QList<Base::Quantity>>& row)
{
addRow(_currentDepth, row);
}
int Material3DArray::addDepth(int depth, const Base::Quantity& value)
{
if (depth == this->depth()) {
// Append to the end
return addDepth(value);
}
if (depth > this->depth()) {
throw InvalidIndex();
}
auto rowVector = std::make_shared<QList<std::shared_ptr<QList<Base::Quantity>>>>();
auto entry = std::make_pair(value, rowVector);
_rowMap.insert(_rowMap.begin() + depth, entry);
return depth;
}
int Material3DArray::addDepth(const Base::Quantity& value)
{
auto rowVector = std::make_shared<QList<std::shared_ptr<QList<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 InvalidIndex
_rowMap.erase(_rowMap.begin() + depth);
}
void Material3DArray::insertRow(int depth,
int row,
const std::shared_ptr<QList<Base::Quantity>>& rowData)
{
try {
auto table = getTable(depth);
table->insert(table->begin() + row, rowData);
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
void Material3DArray::insertRow(int row, const std::shared_ptr<QList<Base::Quantity>>& rowData)
{
insertRow(_currentDepth, row, rowData);
}
void Material3DArray::deleteRow(int depth, int row)
{
auto table = getTable(depth);
if (row >= static_cast<int>(table->size()) || row < 0) {
throw InvalidIndex();
}
table->erase(table->begin() + row);
}
void Material3DArray::deleteRow(int row)
{
deleteRow(_currentDepth, row);
}
void Material3DArray::deleteRows(int depth)
{
auto table = getTable(depth);
table->clear();
}
void Material3DArray::deleteRows()
{
deleteRows(_currentDepth);
}
int Material3DArray::rows(int depth) const
{
if (depth < 0 || (depth == 0 && this->depth() == 0)) {
return 0;
}
validateDepth(depth);
return getTable(depth)->size();
}
void Material3DArray::setValue(int depth, int row, int column, const Base::Quantity& value)
{
validateRow(depth, row);
validateColumn(column);
auto val = getRow(depth, row);
try {
val->replace(column, value);
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
void Material3DArray::setValue(int row, int column, const Base::Quantity& value)
{
setValue(_currentDepth, row, column, value);
}
void Material3DArray::setDepthValue(int depth, const Base::Quantity& value)
{
try {
auto oldRows = getTable(depth);
_rowMap.replace(depth, std::pair(value, oldRows));
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
void Material3DArray::setDepthValue(const Base::Quantity& value)
{
setDepthValue(_currentDepth, value);
}
Base::Quantity Material3DArray::getValue(int depth, int row, int column) const
{
// getRow validates depth and row. Do that first
auto val = getRow(depth, row);
validateColumn(column);
try {
return val->at(column);
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
Base::Quantity Material3DArray::getValue(int row, int column) const
{
return getValue(_currentDepth, row, column);
}
Base::Quantity Material3DArray::getDepthValue(int depth) const
{
validateDepth(depth);
try {
return std::get<0>(_rowMap.at(depth));
}
catch (std::out_of_range const&) {
throw InvalidIndex();
}
}
int Material3DArray::currentDepth() const
{
return _currentDepth;
}
void Material3DArray::setCurrentDepth(int depth)
{
validateDepth(depth);
if (depth < 0 || _rowMap.empty()) {
_currentDepth = 0;
}
else if (depth >= static_cast<int>(_rowMap.size())) {
_currentDepth = _rowMap.size() - 1;
}
else {
_currentDepth = depth;
}
}
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 array contents
QString yaml = QStringLiteral("\n - [");
for (int depth = 0; depth < this->depth(); depth++) {
if (depth > 0) {
// Each row is on its own line, padded for correct indentation
yaml += QStringLiteral(",\n") + pad;
}
yaml += QStringLiteral("\"");
auto value = QString::fromStdString(getDepthValue(depth).getUserString());
yaml += value;
yaml += QStringLiteral("\": [");
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 += QStringLiteral(",\n") + pad2;
}
else {
firstRow = false;
}
yaml += QStringLiteral("[");
bool first = true;
for (auto& column : *row) {
if (!first) {
// TODO: Fix for arrays with too many columns to fit on a single line
yaml += QStringLiteral(", ");
}
else {
first = false;
}
yaml += QStringLiteral("\"");
// Base::Quantity quantity = column.value<Base::Quantity>();
yaml += QString::fromStdString(column.getUserString());
yaml += QStringLiteral("\"");
}
yaml += QStringLiteral("]");
}
yaml += QStringLiteral("]");
}
yaml += QStringLiteral("]");
return yaml;
}