1
/***************************************************************************
2
* Copyright (c) 2023 David Carter <dcarter@david.carter.ca> *
4
* This file is part of FreeCAD. *
6
* FreeCAD is free software: you can redistribute it and/or modify it *
7
* under the terms of the GNU Lesser General Public License as *
8
* published by the Free Software Foundation, either version 2.1 of the *
9
* License, or (at your option) any later version. *
11
* FreeCAD is distributed in the hope that it will be useful, but *
12
* WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14
* Lesser General Public License for more details. *
16
* You should have received a copy of the GNU Lesser General Public *
17
* License along with FreeCAD. If not, see *
18
* <https://www.gnu.org/licenses/>. *
20
**************************************************************************/
22
#include "PreCompiled.h"
25
#include <QRegularExpression>
28
#include <App/Application.h>
29
#include <Base/QtTools.h>
30
#include <Base/Quantity.h>
31
#include <Gui/MetaTypes.h>
33
#include "Exceptions.h"
34
#include "MaterialValue.h"
37
using namespace Materials;
39
/* TRANSLATOR Material::MaterialValue */
41
TYPESYSTEM_SOURCE(Materials::MaterialValue, Base::BaseClass)
43
QMap<QString, MaterialValue::ValueType> MaterialValue::_typeMap {
44
{QString::fromStdString("String"), String},
45
{QString::fromStdString("Boolean"), Boolean},
46
{QString::fromStdString("Integer"), Integer},
47
{QString::fromStdString("Float"), Float},
48
{QString::fromStdString("Quantity"), Quantity},
49
{QString::fromStdString("Distribution"), Distribution},
50
{QString::fromStdString("List"), List},
51
{QString::fromStdString("2DArray"), Array2D},
52
{QString::fromStdString("3DArray"), Array3D},
53
{QString::fromStdString("Color"), Color},
54
{QString::fromStdString("Image"), Image},
55
{QString::fromStdString("File"), File},
56
{QString::fromStdString("URL"), URL},
57
{QString::fromStdString("MultiLineString"), MultiLineString},
58
{QString::fromStdString("FileList"), FileList},
59
{QString::fromStdString("ImageList"), ImageList},
60
{QString::fromStdString("SVG"), SVG}};
62
MaterialValue::MaterialValue()
65
this->setInitialValue(None);
68
MaterialValue::MaterialValue(const MaterialValue& other)
69
: _valueType(other._valueType)
70
, _value(other._value)
73
MaterialValue::MaterialValue(ValueType type)
76
this->setInitialValue(None);
79
MaterialValue::MaterialValue(ValueType type, ValueType inherited)
82
this->setInitialValue(inherited);
85
MaterialValue& MaterialValue::operator=(const MaterialValue& other)
91
_valueType = other._valueType;
92
_value = other._value;
97
bool MaterialValue::operator==(const MaterialValue& other) const
103
return (_valueType == other._valueType) && (_value == other._value);
106
QString MaterialValue::escapeString(const QString& source)
108
QString res = source;
109
res.replace(QString::fromStdString("\\"), QString::fromStdString("\\\\"));
110
res.replace(QString::fromStdString("\""), QString::fromStdString("\\\""));
114
MaterialValue::ValueType MaterialValue::mapType(const QString& stringType)
116
// If not found, return None
117
return _typeMap.value(stringType, None);
120
void MaterialValue::setInitialValue(ValueType inherited)
122
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
123
if (_valueType == String || _valueType == MultiLineString || _valueType == SVG) {
124
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
126
else if (_valueType == Boolean) {
127
_value = QVariant(static_cast<QVariant::Type>(QMetaType::Bool));
129
else if (_valueType == Integer) {
130
_value = QVariant(static_cast<QVariant::Type>(QMetaType::Int));
132
else if (_valueType == Float) {
133
_value = QVariant(static_cast<QVariant::Type>(QMetaType::Float));
135
else if (_valueType == URL) {
136
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
138
else if (_valueType == Color) {
139
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
141
else if (_valueType == File) {
142
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
144
else if (_valueType == Image) {
145
_value = QVariant(static_cast<QVariant::Type>(QMetaType::QString));
148
if (_valueType == String || _valueType == MultiLineString || _valueType == SVG) {
149
_value = QVariant(QMetaType(QMetaType::QString));
151
else if (_valueType == Boolean) {
152
_value = QVariant(QMetaType(QMetaType::Bool));
154
else if (_valueType == Integer) {
155
_value = QVariant(QMetaType(QMetaType::Int));
157
else if (_valueType == Float) {
158
_value = QVariant(QMetaType(QMetaType::Float));
160
else if (_valueType == URL) {
161
_value = QVariant(QMetaType(QMetaType::QString));
163
else if (_valueType == Color) {
164
_value = QVariant(QMetaType(QMetaType::QString));
166
else if (_valueType == File) {
167
_value = QVariant(QMetaType(QMetaType::QString));
169
else if (_valueType == Image) {
170
_value = QVariant(QMetaType(QMetaType::QString));
173
else if (_valueType == Quantity) {
176
_value = QVariant::fromValue(qu);
178
else if (_valueType == List || _valueType == FileList || _valueType == ImageList) {
179
auto list = QList<QVariant>();
180
_value = QVariant::fromValue(list);
182
else if (_valueType == Array2D) {
183
if (_valueType != inherited) {
184
throw InvalidMaterialType("Initializing a regular material value as a 2D Array");
187
_value = QVariant(); // Uninitialized default value
189
else if (_valueType == Array3D) {
190
if (_valueType != inherited) {
191
throw InvalidMaterialType("Initializing a regular material value as a 3D Array");
194
_value = QVariant(); // Uninitialized default value
197
// Default is to set the type to None and leave the variant uninitialized
203
void MaterialValue::setList(const QList<QVariant>& value)
205
_value = QVariant::fromValue(value);
208
bool MaterialValue::isNull() const
210
if (_value.isNull()) {
214
if (_valueType == Quantity) {
215
return !_value.value<Base::Quantity>().isValid();
218
if (_valueType == List || _valueType == FileList || _valueType == ImageList) {
219
return _value.value<QList<QVariant>>().isEmpty();
225
QString MaterialValue::getYAMLStringImage() const
228
yaml = QString::fromStdString(" |-2");
229
QString base64 = getValue().toString();
230
while (!base64.isEmpty()) {
231
yaml += QString::fromStdString("\n ") + base64.left(74);
232
base64.remove(0, 74);
237
QString MaterialValue::getYAMLStringList() const
240
for (auto& it : getList()) {
241
yaml += QString::fromStdString("\n - \"") + escapeString(it.toString())
242
+ QString::fromStdString("\"");
247
QString MaterialValue::getYAMLStringImageList() const
250
for (auto& it : getList()) {
251
yaml += QString::fromStdString("\n - |-2");
252
QString base64 = it.toString();
253
while (!base64.isEmpty()) {
254
yaml += QString::fromStdString("\n ") + base64.left(72);
255
base64.remove(0, 72);
261
QString MaterialValue::getYAMLStringMultiLine() const
264
yaml = QString::fromStdString(" |2");
266
getValue().toString().split(QRegularExpression(QString::fromStdString("[\r\n]")), Qt::SkipEmptyParts);
267
for (auto& it : list) {
268
yaml += QString::fromStdString("\n ") + it;
273
QString MaterialValue::getYAMLString() const
277
if (getType() == MaterialValue::Image) {
278
return getYAMLStringImage();
280
if (getType() == MaterialValue::List || getType() == MaterialValue::FileList) {
281
return getYAMLStringList();
283
if (getType() == MaterialValue::ImageList) {
284
return getYAMLStringImageList();
286
if (getType() == MaterialValue::MultiLineString || getType() == MaterialValue::SVG) {
287
return getYAMLStringMultiLine();
289
if (getType() == MaterialValue::Quantity) {
290
auto quantity = getValue().value<Base::Quantity>();
291
yaml += quantity.getUserString();
293
else if (getType() == MaterialValue::Float) {
294
auto value = getValue();
295
if (!value.isNull()) {
296
yaml += QString::fromLatin1("%1").arg(value.toFloat(), 0, 'g', 6);
299
else if (getType() == MaterialValue::List) {
300
for (auto& it : getList()) {
301
yaml += QString::fromLatin1("\n - \"") + escapeString(it.toString())
302
+ QString::fromLatin1("\"");
307
yaml += getValue().toString();
310
yaml = QString::fromLatin1(" \"") + escapeString(yaml) + QString::fromLatin1("\"");
316
TYPESYSTEM_SOURCE(Materials::Material2DArray, Materials::MaterialValue)
318
Material2DArray::Material2DArray()
319
: MaterialValue(Array2D, Array2D)
322
// Initialize separatelt to prevent recursion
326
Material2DArray::Material2DArray(const Material2DArray& other)
327
: MaterialValue(other)
328
, _columns(other._columns)
333
Material2DArray& Material2DArray::operator=(const Material2DArray& other)
335
if (this == &other) {
339
MaterialValue::operator=(other);
340
_columns = other._columns;
347
void Material2DArray::deepCopy(const Material2DArray& other)
350
for (auto& row : other._rows) {
352
for (auto& col : *row) {
353
QVariant newVariant(col);
354
vv.push_back(newVariant);
356
addRow(std::make_shared<QList<QVariant>>(vv));
360
bool Material2DArray::isNull() const
365
void Material2DArray::validateRow(int row) const
367
if (row < 0 || row >= rows()) {
368
throw InvalidIndex();
372
void Material2DArray::validateColumn(int column) const
374
if (column < 0 || column >= columns()) {
375
throw InvalidIndex();
379
std::shared_ptr<QList<QVariant>> Material2DArray::getRow(int row) const
384
return _rows.at(row);
386
catch (std::out_of_range const&) {
387
throw InvalidIndex();
391
std::shared_ptr<QList<QVariant>> Material2DArray::getRow(int row)
396
return _rows.at(row);
398
catch (std::out_of_range const&) {
399
throw InvalidIndex();
403
void Material2DArray::addRow(const std::shared_ptr<QList<QVariant>>& row)
405
_rows.push_back(row);
408
void Material2DArray::insertRow(int index, const std::shared_ptr<QList<QVariant>>& row)
410
_rows.insert(_rows.begin() + index, row);
413
void Material2DArray::deleteRow(int row)
415
if (row >= static_cast<int>(_rows.size()) || row < 0) {
416
throw InvalidIndex();
418
_rows.erase(_rows.begin() + row);
421
void Material2DArray::setValue(int row, int column, const QVariant& value)
424
validateColumn(column);
426
auto val = getRow(row);
428
val->replace(column, value);
430
catch (const std::out_of_range&) {
431
throw InvalidIndex();
435
QVariant Material2DArray::getValue(int row, int column) const
437
validateColumn(column);
439
auto val = getRow(row);
441
return val->at(column);
443
catch (std::out_of_range const&) {
444
throw InvalidIndex();
448
void Material2DArray::dumpRow(const std::shared_ptr<QList<QVariant>>& row)
450
Base::Console().Log("row: ");
451
for (auto& column : *row) {
452
Base::Console().Log("'%s' ", column.toString().toStdString().c_str());
454
Base::Console().Log("\n");
457
void Material2DArray::dump() const
459
for (auto& row : _rows) {
464
QString Material2DArray::getYAMLString() const
470
// Set the correct indentation. 9 chars in this case
472
pad.fill(QChar::fromLatin1(' '), 9);
474
// Save the array contents
475
QString yaml = QString::fromStdString("\n - [");
476
bool firstRow = true;
477
for (auto& row : _rows) {
479
// Each row is on its own line, padded for correct indentation
480
yaml += QString::fromStdString(",\n") + pad;
485
yaml += QString::fromStdString("[");
488
for (auto& column : *row) {
490
// TODO: Fix for arrays with too many columns to fit on a single line
491
yaml += QString::fromStdString(", ");
496
yaml += QString::fromStdString("\"");
497
auto quantity = column.value<Base::Quantity>();
498
yaml += quantity.getUserString();
499
yaml += QString::fromStdString("\"");
502
yaml += QString::fromStdString("]");
504
yaml += QString::fromStdString("]");
510
TYPESYSTEM_SOURCE(Materials::Material3DArray, Materials::MaterialValue)
512
Material3DArray::Material3DArray()
513
: MaterialValue(Array3D, Array3D)
517
// Initialize separatelt to prevent recursion
521
bool Material3DArray::isNull() const
526
void Material3DArray::validateDepth(int level) const
528
if (level < 0 || level >= depth()) {
529
throw InvalidIndex();
533
void Material3DArray::validateColumn(int column) const
535
if (column < 0 || column >= columns()) {
536
throw InvalidIndex();
540
void Material3DArray::validateRow(int level, int row) const
542
validateDepth(level);
544
if (row < 0 || row >= rows(level)) {
545
throw InvalidIndex();
549
const std::shared_ptr<QList<std::shared_ptr<QList<Base::Quantity>>>>&
550
Material3DArray::getTable(const Base::Quantity& depth) const
552
for (auto& it : _rowMap) {
553
if (std::get<0>(it) == depth) {
554
return std::get<1>(it);
558
throw InvalidIndex();
561
const std::shared_ptr<QList<std::shared_ptr<QList<Base::Quantity>>>>&
562
Material3DArray::getTable(int depthIndex) const
565
return std::get<1>(_rowMap.at(depthIndex));
567
catch (std::out_of_range const&) {
568
throw InvalidIndex();
572
std::shared_ptr<QList<Base::Quantity>> Material3DArray::getRow(int depth, int row) const
574
validateRow(depth, row);
577
return getTable(depth)->at(row);
579
catch (std::out_of_range const&) {
580
throw InvalidIndex();
584
std::shared_ptr<QList<Base::Quantity>> Material3DArray::getRow(int row) const
586
// Check if we can convert otherwise throw error
587
return getRow(_currentDepth, row);
590
std::shared_ptr<QList<Base::Quantity>> Material3DArray::getRow(int depth, int row)
592
validateRow(depth, row);
595
return getTable(depth)->at(row);
597
catch (std::out_of_range const&) {
598
throw InvalidIndex();
602
std::shared_ptr<QList<Base::Quantity>> Material3DArray::getRow(int row)
604
return getRow(_currentDepth, row);
607
void Material3DArray::addRow(int depth, const std::shared_ptr<QList<Base::Quantity>>& row)
610
getTable(depth)->push_back(row);
612
catch (std::out_of_range const&) {
613
throw InvalidIndex();
617
void Material3DArray::addRow(const std::shared_ptr<QList<Base::Quantity>>& row)
619
addRow(_currentDepth, row);
622
int Material3DArray::addDepth(int depth, const Base::Quantity& value)
624
if (depth == this->depth()) {
626
return addDepth(value);
628
if (depth > this->depth()) {
629
throw InvalidIndex();
631
auto rowVector = std::make_shared<QList<std::shared_ptr<QList<Base::Quantity>>>>();
632
auto entry = std::make_pair(value, rowVector);
633
_rowMap.insert(_rowMap.begin() + depth, entry);
638
int Material3DArray::addDepth(const Base::Quantity& value)
640
auto rowVector = std::make_shared<QList<std::shared_ptr<QList<Base::Quantity>>>>();
641
auto entry = std::make_pair(value, rowVector);
642
_rowMap.push_back(entry);
647
void Material3DArray::deleteDepth(int depth)
649
deleteRows(depth); // This may throw an InvalidIndex
650
_rowMap.erase(_rowMap.begin() + depth);
653
void Material3DArray::insertRow(int depth,
655
const std::shared_ptr<QList<Base::Quantity>>& rowData)
658
auto table = getTable(depth);
659
table->insert(table->begin() + row, rowData);
661
catch (std::out_of_range const&) {
662
throw InvalidIndex();
666
void Material3DArray::insertRow(int row, const std::shared_ptr<QList<Base::Quantity>>& rowData)
668
insertRow(_currentDepth, row, rowData);
671
void Material3DArray::deleteRow(int depth, int row)
673
auto table = getTable(depth);
674
if (row >= static_cast<int>(table->size()) || row < 0) {
675
throw InvalidIndex();
677
table->erase(table->begin() + row);
680
void Material3DArray::deleteRow(int row)
682
deleteRow(_currentDepth, row);
685
void Material3DArray::deleteRows(int depth)
687
auto table = getTable(depth);
691
void Material3DArray::deleteRows()
693
deleteRows(_currentDepth);
696
int Material3DArray::rows(int depth) const
698
if (depth < 0 || (depth == 0 && this->depth() == 0)) {
701
validateDepth(depth);
703
return getTable(depth)->size();
706
void Material3DArray::setValue(int depth, int row, int column, const Base::Quantity& value)
708
validateRow(depth, row);
709
validateColumn(column);
711
auto val = getRow(depth, row);
713
val->replace(column, value);
715
catch (std::out_of_range const&) {
716
throw InvalidIndex();
720
void Material3DArray::setValue(int row, int column, const Base::Quantity& value)
722
setValue(_currentDepth, row, column, value);
725
void Material3DArray::setDepthValue(int depth, const Base::Quantity& value)
728
auto oldRows = getTable(depth);
729
_rowMap.replace(depth, std::pair(value, oldRows));
731
catch (std::out_of_range const&) {
732
throw InvalidIndex();
736
void Material3DArray::setDepthValue(const Base::Quantity& value)
738
setDepthValue(_currentDepth, value);
742
Base::Quantity Material3DArray::getValue(int depth, int row, int column) const
744
// getRow validates depth and row. Do that first
745
auto val = getRow(depth, row);
746
validateColumn(column);
749
return val->at(column);
751
catch (std::out_of_range const&) {
752
throw InvalidIndex();
756
Base::Quantity Material3DArray::getValue(int row, int column) const
758
return getValue(_currentDepth, row, column);
761
Base::Quantity Material3DArray::getDepthValue(int depth) const
763
validateDepth(depth);
766
return std::get<0>(_rowMap.at(depth));
768
catch (std::out_of_range const&) {
769
throw InvalidIndex();
773
int Material3DArray::currentDepth() const
775
return _currentDepth;
778
void Material3DArray::setCurrentDepth(int depth)
780
validateDepth(depth);
782
if (depth < 0 || _rowMap.empty()) {
785
else if (depth >= static_cast<int>(_rowMap.size())) {
786
_currentDepth = _rowMap.size() - 1;
789
_currentDepth = depth;
793
QString Material3DArray::getYAMLString() const
799
// Set the correct indentation. 7 chars + name length
801
pad.fill(QChar::fromLatin1(' '), 9);
803
// Save the array contents
804
QString yaml = QString::fromStdString("\n - [");
805
for (int depth = 0; depth < this->depth(); depth++) {
807
// Each row is on its own line, padded for correct indentation
808
yaml += QString::fromStdString(",\n") + pad;
811
yaml += QString::fromStdString("\"");
812
auto value = getDepthValue(depth).getUserString();
814
yaml += QString::fromStdString("\": [");
817
pad2.fill(QChar::fromLatin1(' '), 14 + value.length());
819
bool firstRow = true;
820
auto rows = getTable(depth);
821
for (auto& row : *rows) {
823
// Each row is on its own line, padded for correct indentation
824
yaml += QString::fromStdString(",\n") + pad2;
829
yaml += QString::fromStdString("[");
832
for (auto& column : *row) {
834
// TODO: Fix for arrays with too many columns to fit on a single line
835
yaml += QString::fromStdString(", ");
840
yaml += QString::fromStdString("\"");
841
// Base::Quantity quantity = column.value<Base::Quantity>();
842
yaml += column.getUserString();
843
yaml += QString::fromStdString("\"");
846
yaml += QString::fromStdString("]");
848
yaml += QString::fromStdString("]");
850
yaml += QString::fromStdString("]");