* [ Material ]: Update SPDX License Identifiers * [ Material ]: Correct Test Material License --------- Co-authored-by: Max Wilfinger <6246609+maxwxyz@users.noreply.github.com>
1897 lines
50 KiB
C++
1897 lines
50 KiB
C++
// SPDX-License-Identifier: LGPL-2.1-or-later
|
|
|
|
/***************************************************************************
|
|
* 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 <QMetaType>
|
|
#include <QUuid>
|
|
|
|
|
|
|
|
#include <App/Application.h>
|
|
#include <Gui/MetaTypes.h>
|
|
|
|
#include "Materials.h"
|
|
|
|
#include "MaterialLibrary.h"
|
|
#include "MaterialManager.h"
|
|
#include "ModelManager.h"
|
|
#include "ModelUuids.h"
|
|
|
|
|
|
using namespace Materials;
|
|
|
|
/* TRANSLATOR Material::Materials */
|
|
|
|
TYPESYSTEM_SOURCE(Materials::MaterialProperty, Materials::ModelProperty)
|
|
|
|
MaterialProperty::MaterialProperty()
|
|
{
|
|
_valuePtr = std::make_shared<MaterialValue>(MaterialValue::None);
|
|
}
|
|
|
|
MaterialProperty::MaterialProperty(const ModelProperty& other, QString modelUUID)
|
|
: ModelProperty(other)
|
|
, _modelUUID(modelUUID)
|
|
, _valuePtr(nullptr)
|
|
{
|
|
setType(getPropertyType());
|
|
auto columns = other.getColumns();
|
|
for (auto& it : columns) {
|
|
MaterialProperty prop(it, modelUUID);
|
|
addColumn(prop);
|
|
}
|
|
}
|
|
|
|
void MaterialProperty::copyValuePtr(const std::shared_ptr<MaterialValue>& value)
|
|
{
|
|
if (value->getType() == MaterialValue::Array2D) {
|
|
_valuePtr =
|
|
std::make_shared<Array2D>(*(std::static_pointer_cast<Array2D>(value)));
|
|
}
|
|
else if (value->getType() == MaterialValue::Array3D) {
|
|
_valuePtr =
|
|
std::make_shared<Array3D>(*(std::static_pointer_cast<Array3D>(value)));
|
|
}
|
|
else {
|
|
_valuePtr = std::make_shared<MaterialValue>(*value);
|
|
}
|
|
}
|
|
|
|
MaterialProperty::MaterialProperty(const MaterialProperty& other)
|
|
: ModelProperty(other)
|
|
, _modelUUID(other._modelUUID)
|
|
{
|
|
copyValuePtr(other._valuePtr);
|
|
|
|
for (auto& it : other._columns) {
|
|
_columns.push_back(it);
|
|
}
|
|
}
|
|
|
|
MaterialProperty::MaterialProperty(const std::shared_ptr<MaterialProperty>& other)
|
|
: MaterialProperty(*other)
|
|
{}
|
|
|
|
void MaterialProperty::setModelUUID(const QString& uuid)
|
|
{
|
|
_modelUUID = uuid;
|
|
}
|
|
|
|
QVariant MaterialProperty::getValue()
|
|
{
|
|
return _valuePtr->getValue();
|
|
}
|
|
|
|
QVariant MaterialProperty::getValue() const
|
|
{
|
|
return _valuePtr->getValue();
|
|
}
|
|
|
|
std::shared_ptr<MaterialValue> MaterialProperty::getMaterialValue()
|
|
{
|
|
return _valuePtr;
|
|
}
|
|
|
|
std::shared_ptr<MaterialValue> MaterialProperty::getMaterialValue() const
|
|
{
|
|
return _valuePtr;
|
|
}
|
|
|
|
QString MaterialProperty::getString() const
|
|
{
|
|
// This method produces a localized string. For a non-localized string use
|
|
// getDictionaryString()
|
|
if (isNull()) {
|
|
return {};
|
|
}
|
|
if (getType() == MaterialValue::Quantity) {
|
|
auto quantity = getValue().value<Base::Quantity>();
|
|
return QString::fromStdString(quantity.getUserString());
|
|
}
|
|
if (getType() == MaterialValue::Float) {
|
|
auto value = getValue();
|
|
if (value.isNull()) {
|
|
return {};
|
|
}
|
|
return QString(QStringLiteral("%L1")).arg(value.toFloat(), 0, 'g', MaterialValue::PRECISION);
|
|
}
|
|
return getValue().toString();
|
|
}
|
|
|
|
QString MaterialProperty::getYAMLString() const
|
|
{
|
|
return _valuePtr->getYAMLString();
|
|
}
|
|
|
|
Base::Color MaterialProperty::getColor() const
|
|
{
|
|
auto colorString = getValue().toString();
|
|
std::stringstream stream(colorString.toStdString());
|
|
|
|
char c;
|
|
stream >> c; // read "("
|
|
float red;
|
|
stream >> red;
|
|
stream >> c; // ","
|
|
float green;
|
|
stream >> green;
|
|
stream >> c; // ","
|
|
float blue;
|
|
stream >> blue;
|
|
stream >> c; // ","
|
|
float alpha = 1.0;
|
|
if (c == ',') {
|
|
stream >> alpha;
|
|
}
|
|
|
|
Base::Color color(red, green, blue, alpha);
|
|
return color;
|
|
}
|
|
|
|
|
|
QString MaterialProperty::getDictionaryString() const
|
|
{
|
|
// This method produces a non-localized string. For a localized string use
|
|
// getString()
|
|
if (isNull()) {
|
|
return {};
|
|
}
|
|
if (getType() == MaterialValue::Quantity) {
|
|
auto quantity = getValue().value<Base::Quantity>();
|
|
auto string = QString(QStringLiteral("%1 %2"))
|
|
.arg(quantity.getValue(), 0, 'g', MaterialValue::PRECISION)
|
|
.arg(QString::fromStdString(quantity.getUnit().getString()));
|
|
return string;
|
|
}
|
|
if (getType() == MaterialValue::Float) {
|
|
auto value = getValue();
|
|
if (value.isNull()) {
|
|
return {};
|
|
}
|
|
return QString(QStringLiteral("%1")).arg(value.toFloat(), 0, 'g', MaterialValue::PRECISION);
|
|
}
|
|
return getValue().toString();
|
|
}
|
|
|
|
void MaterialProperty::setPropertyType(const QString& type)
|
|
{
|
|
ModelProperty::setPropertyType(type);
|
|
setType(type);
|
|
}
|
|
|
|
void MaterialProperty::setType(const QString& type)
|
|
{
|
|
auto mappedType = MaterialValue::mapType(type);
|
|
if (mappedType == MaterialValue::None) {
|
|
throw UnknownValueType();
|
|
}
|
|
if (mappedType == MaterialValue::Array2D) {
|
|
auto arrayPtr = std::make_shared<Array2D>();
|
|
arrayPtr->setColumns(columns());
|
|
_valuePtr = arrayPtr;
|
|
}
|
|
else if (mappedType == MaterialValue::Array3D) {
|
|
auto arrayPtr = std::make_shared<Array3D>();
|
|
// First column is third dimension
|
|
arrayPtr->setColumns(columns() - 1);
|
|
_valuePtr = arrayPtr;
|
|
}
|
|
else {
|
|
_valuePtr = std::make_shared<MaterialValue>(mappedType);
|
|
}
|
|
}
|
|
|
|
MaterialProperty& MaterialProperty::getColumn(int column)
|
|
{
|
|
try {
|
|
return _columns.at(column);
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw InvalidIndex();
|
|
}
|
|
}
|
|
|
|
const MaterialProperty& MaterialProperty::getColumn(int column) const
|
|
{
|
|
try {
|
|
return _columns.at(column);
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw InvalidIndex();
|
|
}
|
|
}
|
|
|
|
MaterialValue::ValueType MaterialProperty::getColumnType(int column) const
|
|
{
|
|
try {
|
|
return _columns.at(column).getType();
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw InvalidIndex();
|
|
}
|
|
}
|
|
|
|
QString MaterialProperty::getColumnUnits(int column) const
|
|
{
|
|
try {
|
|
return _columns.at(column).getUnits();
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw InvalidIndex();
|
|
}
|
|
}
|
|
|
|
QVariant MaterialProperty::getColumnNull(int column) const
|
|
{
|
|
MaterialValue::ValueType valueType = getColumnType(column);
|
|
|
|
switch (valueType) {
|
|
case MaterialValue::Quantity: {
|
|
Base::Quantity quant = Base::Quantity(0, getColumnUnits(column).toStdString());
|
|
return QVariant::fromValue(quant);
|
|
}
|
|
|
|
case MaterialValue::Float:
|
|
case MaterialValue::Integer:
|
|
return 0;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QString();
|
|
}
|
|
|
|
void MaterialProperty::setValue(const QVariant& value)
|
|
{
|
|
if (_valuePtr->getType() == MaterialValue::Quantity && value.canConvert<Base::Quantity>()) {
|
|
// Ensure the units are set correctly
|
|
auto quantity = value.value<Base::Quantity>();
|
|
if (quantity.isValid()) {
|
|
setQuantity(quantity);
|
|
}
|
|
else {
|
|
// Set a default value with default units
|
|
setValue(QStringLiteral("0"));
|
|
}
|
|
}
|
|
else {
|
|
_valuePtr->setValue(value);
|
|
}
|
|
}
|
|
|
|
void MaterialProperty::setValue(const QString& value)
|
|
{
|
|
if (_valuePtr->getType() == MaterialValue::Boolean) {
|
|
setBoolean(value);
|
|
}
|
|
else if (_valuePtr->getType() == MaterialValue::Integer) {
|
|
setInt(value);
|
|
}
|
|
else if (_valuePtr->getType() == MaterialValue::Float) {
|
|
setFloat(value);
|
|
}
|
|
else if (_valuePtr->getType() == MaterialValue::URL) {
|
|
setURL(value);
|
|
}
|
|
else if (_valuePtr->getType() == MaterialValue::Array2D
|
|
|| _valuePtr->getType() == MaterialValue::Array3D) {
|
|
// This value can't be directly assigned
|
|
}
|
|
else if (_valuePtr->getType() == MaterialValue::Quantity) {
|
|
try {
|
|
setQuantity(Base::Quantity::parse(value.toStdString()));
|
|
}
|
|
catch (const Base::ParserError& e) {
|
|
Base::Console().log("MaterialProperty::setValue Error '%s' - '%s'\n",
|
|
e.what(),
|
|
value.toStdString().c_str());
|
|
// Save as a string
|
|
setString(value);
|
|
}
|
|
}
|
|
else {
|
|
setString(value);
|
|
}
|
|
}
|
|
|
|
void MaterialProperty::setValue(const std::shared_ptr<MaterialValue>& value)
|
|
{
|
|
_valuePtr = value;
|
|
}
|
|
|
|
void MaterialProperty::setString(const QString& value)
|
|
{
|
|
_valuePtr->setValue(QVariant(value));
|
|
}
|
|
|
|
void MaterialProperty::setString(const std::string& value)
|
|
{
|
|
_valuePtr->setValue(QVariant(QString::fromStdString(value)));
|
|
}
|
|
|
|
void MaterialProperty::setBoolean(bool value)
|
|
{
|
|
_valuePtr->setValue(QVariant(value));
|
|
}
|
|
|
|
void MaterialProperty::setBoolean(int value)
|
|
{
|
|
_valuePtr->setValue(QVariant(value != 0));
|
|
}
|
|
|
|
void MaterialProperty::setBoolean(const QString& value)
|
|
{
|
|
bool boolean = false;
|
|
std::string val = value.toStdString();
|
|
if ((val == "true") || (val == "True")) {
|
|
boolean = true;
|
|
}
|
|
else if ((val == "false") || (val == "False")) {
|
|
boolean = false;
|
|
}
|
|
else {
|
|
boolean = (std::stoi(val) != 0);
|
|
}
|
|
|
|
setBoolean(boolean);
|
|
}
|
|
|
|
void MaterialProperty::setInt(int value)
|
|
{
|
|
_valuePtr->setValue(QVariant(value));
|
|
}
|
|
|
|
void MaterialProperty::setInt(const QString& value)
|
|
{
|
|
_valuePtr->setValue(value.toInt());
|
|
}
|
|
|
|
void MaterialProperty::setFloat(double value)
|
|
{
|
|
_valuePtr->setValue(QVariant(value));
|
|
}
|
|
|
|
void MaterialProperty::setFloat(const QString& value)
|
|
{
|
|
_valuePtr->setValue(QVariant(value.toFloat()));
|
|
}
|
|
|
|
void MaterialProperty::setQuantity(const Base::Quantity& value)
|
|
{
|
|
auto quantity = value;
|
|
if (quantity.isDimensionless()) {
|
|
// Assign the default units when none are provided.
|
|
//
|
|
// This needs to be parsed rather than just setting units. Otherwise we get mm->m conversion
|
|
// errors, etc
|
|
quantity = Base::Quantity::parse(quantity.getUserString() + getUnits().toStdString());
|
|
}
|
|
else {
|
|
auto propertyUnit = Base::Quantity::parse(getUnits().toStdString()).getUnit();
|
|
auto units = quantity.getUnit();
|
|
if (propertyUnit != units) {
|
|
throw Base::ValueError("Incompatible material units");
|
|
}
|
|
}
|
|
quantity.setFormat(MaterialValue::getQuantityFormat());
|
|
_valuePtr->setValue(QVariant(QVariant::fromValue(quantity)));
|
|
}
|
|
|
|
void MaterialProperty::setQuantity(double value, const QString& units)
|
|
{
|
|
setQuantity(Base::Quantity(value, units.toStdString()));
|
|
}
|
|
|
|
void MaterialProperty::setQuantity(const QString& value)
|
|
{
|
|
setQuantity(Base::Quantity::parse(value.toStdString()));
|
|
}
|
|
|
|
void MaterialProperty::setList(const QList<QVariant>& value)
|
|
{
|
|
_valuePtr->setList(value);
|
|
}
|
|
|
|
void MaterialProperty::setURL(const QString& value)
|
|
{
|
|
_valuePtr->setValue(QVariant(value));
|
|
}
|
|
|
|
void MaterialProperty::setColor(const Base::Color& value)
|
|
{
|
|
std::stringstream ss;
|
|
ss << "(" << value.r << ", " << value.g << ", " << value.b << ", " << value.a << ")";
|
|
_valuePtr->setValue(QVariant(QString::fromStdString(ss.str())));
|
|
}
|
|
|
|
MaterialProperty& MaterialProperty::operator=(const MaterialProperty& other)
|
|
{
|
|
if (this == &other) {
|
|
return *this;
|
|
}
|
|
|
|
ModelProperty::operator=(other);
|
|
|
|
_modelUUID = other._modelUUID;
|
|
copyValuePtr(other._valuePtr);
|
|
|
|
_columns.clear();
|
|
for (auto& it : other._columns) {
|
|
_columns.push_back(it);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
bool MaterialProperty::operator==(const MaterialProperty& other) const
|
|
{
|
|
if (this == &other) {
|
|
return true;
|
|
}
|
|
|
|
if (ModelProperty::operator==(other)) {
|
|
return (*_valuePtr == *other._valuePtr);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void MaterialProperty::validate(const MaterialProperty& other) const {
|
|
_valuePtr->validate(*other._valuePtr);
|
|
|
|
if (_columns.size() != other._columns.size()) {
|
|
throw InvalidProperty("Model property column counts don't match");
|
|
}
|
|
for (size_t i = 0; i < _columns.size(); i++) {
|
|
_columns[i].validate(other._columns[i]);
|
|
}
|
|
}
|
|
|
|
TYPESYSTEM_SOURCE(Materials::Material, Base::BaseClass)
|
|
|
|
Material::Material()
|
|
: _dereferenced(false)
|
|
, _oldFormat(false)
|
|
, _editState(ModelEdit_None)
|
|
{
|
|
// Create an initial UUID
|
|
newUuid();
|
|
}
|
|
|
|
Material::Material(const std::shared_ptr<MaterialLibrary>& library,
|
|
const QString& directory,
|
|
const QString& uuid,
|
|
const QString& name)
|
|
: _library(library)
|
|
, _uuid(uuid)
|
|
, _name(name)
|
|
, _dereferenced(false)
|
|
, _oldFormat(false)
|
|
, _editState(ModelEdit_None)
|
|
{
|
|
setDirectory(directory);
|
|
}
|
|
|
|
Material::Material(const Material& other)
|
|
: _library(other._library)
|
|
, _directory(other._directory)
|
|
, _filename(other._filename)
|
|
, _uuid(other._uuid)
|
|
, _name(other._name)
|
|
, _author(other._author)
|
|
, _license(other._license)
|
|
, _parentUuid(other._parentUuid)
|
|
, _description(other._description)
|
|
, _url(other._url)
|
|
, _reference(other._reference)
|
|
, _dereferenced(other._dereferenced)
|
|
, _oldFormat(other._oldFormat)
|
|
, _editState(other._editState)
|
|
{
|
|
for (auto& it : other._tags) {
|
|
_tags.insert(it);
|
|
}
|
|
for (auto& it : other._physicalUuids) {
|
|
_physicalUuids.insert(it);
|
|
}
|
|
for (auto& it : other._appearanceUuids) {
|
|
_appearanceUuids.insert(it);
|
|
}
|
|
for (auto& it : other._allUuids) {
|
|
_allUuids.insert(it);
|
|
}
|
|
for (auto& it : other._physical) {
|
|
MaterialProperty prop(it.second);
|
|
_physical[it.first] = std::make_shared<MaterialProperty>(prop);
|
|
}
|
|
for (auto& it : other._appearance) {
|
|
MaterialProperty prop(it.second);
|
|
_appearance[it.first] = std::make_shared<MaterialProperty>(prop);
|
|
}
|
|
for (auto& it : other._legacy) {
|
|
_legacy[it.first] = it.second;
|
|
}
|
|
}
|
|
|
|
QString Material::getDirectory() const
|
|
{
|
|
return _directory;
|
|
}
|
|
|
|
void Material::setDirectory(const QString& directory)
|
|
{
|
|
_directory = directory;
|
|
}
|
|
|
|
QString Material::getFilename() const
|
|
{
|
|
return _filename;
|
|
}
|
|
|
|
void Material::setFilename(const QString& filename)
|
|
{
|
|
_filename = filename;
|
|
}
|
|
|
|
QString Material::getFilePath() const
|
|
{
|
|
return QDir(_directory + QStringLiteral("/") + _filename).absolutePath();
|
|
}
|
|
|
|
QString Material::getAuthorAndLicense() const
|
|
{
|
|
QString authorAndLicense;
|
|
|
|
// Combine the author and license field for backwards compatibility
|
|
if (!_author.isNull()) {
|
|
authorAndLicense = _author;
|
|
if (!_license.isNull()) {
|
|
authorAndLicense += QStringLiteral(" ") + _license;
|
|
}
|
|
}
|
|
else if (!_license.isNull()) {
|
|
authorAndLicense = _license;
|
|
}
|
|
|
|
return _license;
|
|
}
|
|
|
|
void Material::addModel(const QString& uuid)
|
|
{
|
|
for (const auto& modelUUID : std::as_const(_allUuids)) {
|
|
if (modelUUID == uuid) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
_allUuids << uuid;
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
try {
|
|
auto model = manager.getModel(uuid);
|
|
auto inheritance = model->getInheritance();
|
|
for (auto& inherits : inheritance) {
|
|
addModel(inherits);
|
|
}
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
}
|
|
}
|
|
|
|
void Material::clearModels()
|
|
{
|
|
_physicalUuids.clear();
|
|
_appearanceUuids.clear();
|
|
_allUuids.clear();
|
|
_physical.clear();
|
|
_appearance.clear();
|
|
}
|
|
|
|
void Material::clearInherited()
|
|
{
|
|
_allUuids.clear();
|
|
|
|
// Rebuild the UUID lists without the inherited UUIDs
|
|
for (auto& uuid : _physicalUuids) {
|
|
_allUuids << uuid;
|
|
}
|
|
for (auto& uuid : _appearanceUuids) {
|
|
_allUuids << uuid;
|
|
}
|
|
}
|
|
|
|
void Material::setName(const QString& name)
|
|
{
|
|
_name = name;
|
|
setEditStateExtend();
|
|
}
|
|
|
|
void Material::setAuthor(const QString& author)
|
|
{
|
|
_author = author;
|
|
setEditStateExtend();
|
|
}
|
|
|
|
void Material::setLicense(const QString& license)
|
|
{
|
|
_license = license;
|
|
setEditStateExtend();
|
|
}
|
|
|
|
void Material::setParentUUID(const QString& uuid)
|
|
{
|
|
_parentUuid = uuid;
|
|
setEditStateExtend();
|
|
}
|
|
|
|
void Material::setDescription(const QString& description)
|
|
{
|
|
_description = description;
|
|
setEditStateExtend();
|
|
}
|
|
|
|
void Material::setURL(const QString& url)
|
|
{
|
|
_url = url;
|
|
setEditStateExtend();
|
|
}
|
|
|
|
void Material::setReference(const QString& reference)
|
|
{
|
|
_reference = reference;
|
|
setEditStateExtend();
|
|
}
|
|
|
|
void Material::setEditState(ModelEdit newState)
|
|
{
|
|
if (newState == ModelEdit_Extend) {
|
|
if (_editState != ModelEdit_Alter) {
|
|
_editState = newState;
|
|
}
|
|
}
|
|
else if (newState == ModelEdit_Alter) {
|
|
_editState = newState;
|
|
}
|
|
}
|
|
|
|
void Material::removeUUID(QSet<QString>& uuidList, const QString& uuid)
|
|
{
|
|
uuidList.remove(uuid);
|
|
}
|
|
|
|
void Material::addTag(const QString& tag)
|
|
{
|
|
auto trimmed = tag.trimmed();
|
|
if (!trimmed.isEmpty()) {
|
|
_tags.insert(trimmed);
|
|
}
|
|
}
|
|
|
|
void Material::removeTag(const QString& tag)
|
|
{
|
|
_tags.remove(tag);
|
|
}
|
|
|
|
void Material::addPhysical(const QString& uuid)
|
|
{
|
|
if (hasPhysicalModel(uuid)) {
|
|
return;
|
|
}
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
try {
|
|
auto model = manager.getModel(uuid);
|
|
|
|
auto& inheritance = model->getInheritance();
|
|
for (auto& it : inheritance) {
|
|
// Inherited models may already have the properties, so just
|
|
// remove the uuid
|
|
removeUUID(_physicalUuids, it);
|
|
}
|
|
|
|
_physicalUuids.insert(uuid);
|
|
addModel(uuid);
|
|
setEditStateExtend();
|
|
|
|
for (auto& it : *model) {
|
|
QString propertyName = it.first;
|
|
if (!hasPhysicalProperty(propertyName)) {
|
|
ModelProperty property = static_cast<ModelProperty>(it.second);
|
|
|
|
try {
|
|
_physical[propertyName] = std::make_shared<MaterialProperty>(property, uuid);
|
|
}
|
|
catch (const UnknownValueType&) {
|
|
Base::Console().error("Property '%s' has unknown type '%s'. Ignoring\n",
|
|
property.getName().toStdString().c_str(),
|
|
property.getPropertyType().toStdString().c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
}
|
|
}
|
|
|
|
void Material::removePhysical(const QString& uuid)
|
|
{
|
|
if (!hasPhysicalModel(uuid)) {
|
|
return;
|
|
}
|
|
|
|
// If it's an inherited model, do nothing
|
|
if (isInherited(uuid)) {
|
|
return;
|
|
}
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
try {
|
|
auto model = manager.getModel(uuid);
|
|
|
|
auto& inheritance = model->getInheritance();
|
|
for (auto& it : inheritance) {
|
|
removeUUID(_physicalUuids, it);
|
|
removeUUID(_allUuids, it);
|
|
}
|
|
removeUUID(_physicalUuids, uuid);
|
|
removeUUID(_allUuids, uuid);
|
|
|
|
for (auto& it : *model) {
|
|
_physical.erase(it.first);
|
|
}
|
|
|
|
setEditStateAlter();
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
Base::Console().log("Physical model not found '%s'\n", uuid.toStdString().c_str());
|
|
}
|
|
}
|
|
|
|
void Material::addAppearance(const QString& uuid)
|
|
{
|
|
if (hasAppearanceModel(uuid)) {
|
|
return;
|
|
}
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
try {
|
|
auto model = manager.getModel(uuid);
|
|
|
|
auto& inheritance = model->getInheritance();
|
|
for (auto& it : inheritance) {
|
|
// Inherited models may already have the properties, so just
|
|
// remove the uuid
|
|
removeUUID(_appearanceUuids, it);
|
|
}
|
|
|
|
_appearanceUuids.insert(uuid);
|
|
addModel(uuid);
|
|
setEditStateExtend();
|
|
|
|
for (auto& it : *model) {
|
|
QString propertyName = it.first;
|
|
if (!hasAppearanceProperty(propertyName)) {
|
|
ModelProperty property = static_cast<ModelProperty>(it.second);
|
|
|
|
_appearance[propertyName] = std::make_shared<MaterialProperty>(property, uuid);
|
|
}
|
|
}
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
Base::Console().log("Appearance model not found '%s'\n", uuid.toStdString().c_str());
|
|
}
|
|
}
|
|
|
|
void Material::removeAppearance(const QString& uuid)
|
|
{
|
|
if (!hasAppearanceModel(uuid)) {
|
|
return;
|
|
}
|
|
|
|
// If it's an inherited model, do nothing
|
|
if (isInherited(uuid)) {
|
|
return;
|
|
}
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
try {
|
|
auto model = manager.getModel(uuid);
|
|
|
|
auto& inheritance = model->getInheritance();
|
|
for (auto& it : inheritance) {
|
|
removeUUID(_appearanceUuids, it);
|
|
removeUUID(_allUuids, it);
|
|
}
|
|
removeUUID(_appearanceUuids, uuid);
|
|
removeUUID(_allUuids, uuid);
|
|
|
|
for (auto& it : *model) {
|
|
_appearance.erase(it.first);
|
|
}
|
|
|
|
setEditStateAlter();
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
}
|
|
}
|
|
|
|
void Material::setPropertyEditState(const QString& name)
|
|
{
|
|
try {
|
|
if (hasPhysicalProperty(name)) {
|
|
setPhysicalEditState(name);
|
|
}
|
|
else if (hasAppearanceProperty(name)) {
|
|
setAppearanceEditState(name);
|
|
}
|
|
}
|
|
catch (const PropertyNotFound&) {
|
|
}
|
|
}
|
|
|
|
void Material::setPhysicalEditState(const QString& name)
|
|
{
|
|
if (getPhysicalProperty(name)->isNull()) {
|
|
setEditStateExtend();
|
|
}
|
|
else {
|
|
setEditStateAlter();
|
|
}
|
|
}
|
|
|
|
void Material::setAppearanceEditState(const QString& name)
|
|
{
|
|
try {
|
|
if (getAppearanceProperty(name)->isNull()) {
|
|
setEditStateExtend();
|
|
}
|
|
else {
|
|
setEditStateAlter();
|
|
}
|
|
}
|
|
catch (const PropertyNotFound&) {
|
|
}
|
|
}
|
|
|
|
void Material::setPhysicalValue(const QString& name, const QString& value)
|
|
{
|
|
setPhysicalEditState(name);
|
|
|
|
if (hasPhysicalProperty(name)) {
|
|
_physical[name]->setValue(value); // may not be a string type, conversion may be required
|
|
}
|
|
}
|
|
|
|
void Material::setPhysicalValue(const QString& name, int value)
|
|
{
|
|
setPhysicalEditState(name);
|
|
|
|
if (hasPhysicalProperty(name)) {
|
|
_physical[name]->setInt(value);
|
|
}
|
|
}
|
|
|
|
void Material::setPhysicalValue(const QString& name, double value)
|
|
{
|
|
setPhysicalEditState(name);
|
|
|
|
if (hasPhysicalProperty(name)) {
|
|
_physical[name]->setFloat(value);
|
|
}
|
|
}
|
|
|
|
void Material::setPhysicalValue(const QString& name, const Base::Quantity& value)
|
|
{
|
|
setPhysicalEditState(name);
|
|
|
|
if (hasPhysicalProperty(name)) {
|
|
_physical[name]->setQuantity(value);
|
|
}
|
|
}
|
|
|
|
void Material::setPhysicalValue(const QString& name, const std::shared_ptr<MaterialValue>& value)
|
|
{
|
|
setPhysicalEditState(name);
|
|
|
|
if (hasPhysicalProperty(name)) {
|
|
_physical[name]->setValue(value);
|
|
}
|
|
}
|
|
|
|
void Material::setPhysicalValue(const QString& name, const std::shared_ptr<QList<QVariant>>& value)
|
|
{
|
|
setPhysicalEditState(name);
|
|
|
|
if (hasPhysicalProperty(name)) {
|
|
_physical[name]->setList(*value);
|
|
}
|
|
}
|
|
|
|
void Material::setPhysicalValue(const QString& name, const QVariant& value)
|
|
{
|
|
setPhysicalEditState(name);
|
|
|
|
if (hasPhysicalProperty(name)) {
|
|
_physical[name]->setValue(value);
|
|
}
|
|
}
|
|
|
|
void Material::setAppearanceValue(const QString& name, const QString& value)
|
|
{
|
|
setAppearanceEditState(name);
|
|
|
|
if (hasAppearanceProperty(name)) {
|
|
_appearance[name]->setValue(value); // may not be a string type, conversion may be required
|
|
}
|
|
}
|
|
|
|
void Material::setAppearanceValue(const QString& name, const std::shared_ptr<MaterialValue>& value)
|
|
{
|
|
setAppearanceEditState(name);
|
|
|
|
if (hasAppearanceProperty(name)) {
|
|
_appearance[name]->setValue(value);
|
|
}
|
|
}
|
|
|
|
void Material::setAppearanceValue(const QString& name,
|
|
const std::shared_ptr<QList<QVariant>>& value)
|
|
{
|
|
setAppearanceEditState(name);
|
|
|
|
if (hasAppearanceProperty(name)) {
|
|
_appearance[name]->setList(*value);
|
|
}
|
|
}
|
|
|
|
void Material::setAppearanceValue(const QString& name, const QVariant& value)
|
|
{
|
|
setAppearanceEditState(name);
|
|
|
|
if (hasAppearanceProperty(name)) {
|
|
_appearance[name]->setValue(value);
|
|
}
|
|
}
|
|
|
|
void Material::setValue(const QString& name, const QString& value)
|
|
{
|
|
if (hasPhysicalProperty(name)) {
|
|
setPhysicalValue(name, value);
|
|
}
|
|
else if (hasAppearanceProperty(name)) {
|
|
setAppearanceValue(name, value);
|
|
}
|
|
else {
|
|
throw PropertyNotFound();
|
|
}
|
|
}
|
|
|
|
void Material::setValue(const QString& name, const QVariant& value)
|
|
{
|
|
if (hasPhysicalProperty(name)) {
|
|
setPhysicalValue(name, value);
|
|
}
|
|
else if (hasAppearanceProperty(name)) {
|
|
setAppearanceValue(name, value);
|
|
}
|
|
else {
|
|
throw PropertyNotFound();
|
|
}
|
|
}
|
|
|
|
void Material::setValue(const QString& name, const std::shared_ptr<MaterialValue>& value)
|
|
{
|
|
if (hasPhysicalProperty(name)) {
|
|
setPhysicalValue(name, value);
|
|
}
|
|
else if (hasAppearanceProperty(name)) {
|
|
setAppearanceValue(name, value);
|
|
}
|
|
else {
|
|
throw PropertyNotFound();
|
|
}
|
|
}
|
|
|
|
void Material::setLegacyValue(const QString& name, const QString& value)
|
|
{
|
|
setEditStateAlter();
|
|
|
|
_legacy[name] = value;
|
|
}
|
|
|
|
std::shared_ptr<MaterialProperty> Material::getPhysicalProperty(const QString& name)
|
|
{
|
|
try {
|
|
return _physical.at(name);
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw PropertyNotFound();
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<MaterialProperty> Material::getPhysicalProperty(const QString& name) const
|
|
{
|
|
try {
|
|
return _physical.at(name);
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw PropertyNotFound();
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<MaterialProperty> Material::getAppearanceProperty(const QString& name)
|
|
{
|
|
try {
|
|
return _appearance.at(name);
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw PropertyNotFound();
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<MaterialProperty> Material::getAppearanceProperty(const QString& name) const
|
|
{
|
|
try {
|
|
return _appearance.at(name);
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw PropertyNotFound();
|
|
}
|
|
}
|
|
|
|
std::shared_ptr<MaterialProperty> Material::getProperty(const QString& name)
|
|
{
|
|
if (hasPhysicalProperty(name)) {
|
|
return getPhysicalProperty(name);
|
|
}
|
|
if (hasAppearanceProperty(name)) {
|
|
return getAppearanceProperty(name);
|
|
}
|
|
throw PropertyNotFound();
|
|
}
|
|
|
|
std::shared_ptr<MaterialProperty> Material::getProperty(const QString& name) const
|
|
{
|
|
if (hasPhysicalProperty(name)) {
|
|
return getPhysicalProperty(name);
|
|
}
|
|
if (hasAppearanceProperty(name)) {
|
|
return getAppearanceProperty(name);
|
|
}
|
|
throw PropertyNotFound();
|
|
}
|
|
|
|
QVariant
|
|
Material::getValue(const std::map<QString, std::shared_ptr<MaterialProperty>>& propertyList,
|
|
const QString& name)
|
|
{
|
|
try {
|
|
return propertyList.at(name)->getValue();
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw PropertyNotFound();
|
|
}
|
|
}
|
|
|
|
QString
|
|
Material::getValueString(const std::map<QString, std::shared_ptr<MaterialProperty>>& propertyList,
|
|
const QString& name)
|
|
{
|
|
try {
|
|
const auto& property = propertyList.at(name);
|
|
if (property->isNull()) {
|
|
return {};
|
|
}
|
|
if (property->getType() == MaterialValue::Quantity) {
|
|
auto value = property->getValue();
|
|
if (value.isNull()) {
|
|
return {};
|
|
}
|
|
return QString::fromStdString(value.value<Base::Quantity>().getUserString());
|
|
}
|
|
if (property->getType() == MaterialValue::Float) {
|
|
auto value = property->getValue();
|
|
if (value.isNull()) {
|
|
return {};
|
|
}
|
|
return QString(QStringLiteral("%L1"))
|
|
.arg(value.toFloat(), 0, 'g', MaterialValue::PRECISION);
|
|
}
|
|
return property->getValue().toString();
|
|
}
|
|
catch (std::out_of_range const&) {
|
|
throw PropertyNotFound();
|
|
}
|
|
}
|
|
|
|
QVariant Material::getPhysicalValue(const QString& name) const
|
|
{
|
|
return getValue(_physical, name);
|
|
}
|
|
|
|
Base::Quantity Material::getPhysicalQuantity(const QString& name) const
|
|
{
|
|
return getValue(_physical, name).value<Base::Quantity>();
|
|
}
|
|
|
|
QString Material::getPhysicalValueString(const QString& name) const
|
|
{
|
|
return getValueString(_physical, name);
|
|
}
|
|
|
|
QVariant Material::getAppearanceValue(const QString& name) const
|
|
{
|
|
return getValue(_appearance, name);
|
|
}
|
|
|
|
Base::Quantity Material::getAppearanceQuantity(const QString& name) const
|
|
{
|
|
return getValue(_appearance, name).value<Base::Quantity>();
|
|
}
|
|
|
|
QString Material::getAppearanceValueString(const QString& name) const
|
|
{
|
|
return getValueString(_appearance, name);
|
|
}
|
|
|
|
bool Material::hasPhysicalProperty(const QString& name) const
|
|
{
|
|
return _physical.find(name) != _physical.end();
|
|
}
|
|
|
|
bool Material::hasAppearanceProperty(const QString& name) const
|
|
{
|
|
return _appearance.find(name) != _appearance.end();
|
|
}
|
|
|
|
bool Material::hasNonLegacyProperty(const QString& name) const
|
|
{
|
|
if (hasPhysicalProperty(name) || hasAppearanceProperty(name)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Material::hasLegacyProperties() const
|
|
{
|
|
return !_legacy.empty();
|
|
}
|
|
|
|
bool Material::hasPhysicalProperties() const
|
|
{
|
|
return !_physicalUuids.isEmpty();
|
|
}
|
|
|
|
bool Material::hasAppearanceProperties() const
|
|
{
|
|
return !_appearanceUuids.isEmpty();
|
|
}
|
|
|
|
bool Material::isInherited(const QString& uuid) const
|
|
{
|
|
if (_physicalUuids.contains(uuid)) {
|
|
return false;
|
|
}
|
|
if (_appearanceUuids.contains(uuid)) {
|
|
return false;
|
|
}
|
|
|
|
return _allUuids.contains(uuid);
|
|
}
|
|
|
|
bool Material::hasModel(const QString& uuid) const
|
|
{
|
|
return _allUuids.contains(uuid);
|
|
}
|
|
|
|
bool Material::hasPhysicalModel(const QString& uuid) const
|
|
{
|
|
if (!hasModel(uuid)) {
|
|
return false;
|
|
}
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
try {
|
|
auto model = manager.getModel(uuid);
|
|
if (model->getType() == Model::ModelType_Physical) {
|
|
return true;
|
|
}
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Material::hasAppearanceModel(const QString& uuid) const
|
|
{
|
|
if (!hasModel(uuid)) {
|
|
return false;
|
|
}
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
try {
|
|
auto model = manager.getModel(uuid);
|
|
if (model->getType() == Model::ModelType_Appearance) {
|
|
return true;
|
|
}
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Material::isPhysicalModelComplete(const QString& uuid) const
|
|
{
|
|
if (!hasPhysicalModel(uuid)) {
|
|
return false;
|
|
}
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
try {
|
|
auto model = manager.getModel(uuid);
|
|
for (auto& it : *model) {
|
|
QString propertyName = it.first;
|
|
auto property = getPhysicalProperty(propertyName);
|
|
|
|
if (property->isNull()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Material::isAppearanceModelComplete(const QString& uuid) const
|
|
{
|
|
if (!hasAppearanceModel(uuid)) {
|
|
return false;
|
|
}
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
try {
|
|
auto model = manager.getModel(uuid);
|
|
for (auto& it : *model) {
|
|
QString propertyName = it.first;
|
|
auto property = getAppearanceProperty(propertyName);
|
|
|
|
if (property->isNull()) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void Material::saveGeneral(QTextStream& stream) const
|
|
{
|
|
stream << "General:\n";
|
|
stream << " UUID: \"" << _uuid << "\"\n";
|
|
stream << " Name: \"" << MaterialValue::escapeString(_name) << "\"\n";
|
|
if (!_author.isEmpty()) {
|
|
stream << " Author: \"" << MaterialValue::escapeString(_author) << "\"\n";
|
|
}
|
|
if (!_license.isEmpty()) {
|
|
stream << " License: \"" << MaterialValue::escapeString(_license) << "\"\n";
|
|
}
|
|
if (!_description.isEmpty()) {
|
|
stream << " Description: \"" << MaterialValue::escapeString(_description) << "\"\n";
|
|
}
|
|
if (!_url.isEmpty()) {
|
|
stream << " SourceURL: \"" << MaterialValue::escapeString(_url) << "\"\n";
|
|
}
|
|
if (!_reference.isEmpty()) {
|
|
stream << " ReferenceSource: \"" << MaterialValue::escapeString(_reference) << "\"\n";
|
|
}
|
|
if (!_tags.isEmpty()) {
|
|
stream << " Tags:\n";
|
|
for (auto tag : _tags) {
|
|
stream << " - \"" << tag << "\"\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
void Material::saveInherits(QTextStream& stream) const
|
|
{
|
|
if (!_parentUuid.isEmpty()) {
|
|
try {
|
|
auto material = MaterialManager::getManager().getMaterial(_parentUuid);
|
|
|
|
stream << "Inherits:\n";
|
|
stream << " " << material->getName() << ":\n";
|
|
stream << " UUID: \"" << _parentUuid << "\"\n";
|
|
}
|
|
catch (const MaterialNotFound&) {
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Material::modelChanged(const Material& parent,
|
|
const Model& model) const
|
|
{
|
|
for (auto& it : model) {
|
|
QString propertyName = it.first;
|
|
auto property = getPhysicalProperty(propertyName);
|
|
try {
|
|
auto parentProperty = parent.getPhysicalProperty(propertyName);
|
|
|
|
if (*property != *parentProperty) {
|
|
return true;
|
|
}
|
|
}
|
|
catch (const PropertyNotFound&) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Material::modelAppearanceChanged(const Material& parent,
|
|
const Model& model) const
|
|
{
|
|
for (auto& it : model) {
|
|
QString propertyName = it.first;
|
|
auto property = getAppearanceProperty(propertyName);
|
|
try {
|
|
auto parentProperty = parent.getAppearanceProperty(propertyName);
|
|
|
|
if (*property != *parentProperty) {
|
|
return true;
|
|
}
|
|
}
|
|
catch (const PropertyNotFound&) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Material::saveModels(QTextStream& stream, bool saveInherited) const
|
|
{
|
|
if (_physical.empty()) {
|
|
return;
|
|
}
|
|
|
|
auto& modelManager = ModelManager::getManager();
|
|
auto& materialManager = MaterialManager::getManager();
|
|
|
|
bool inherited = saveInherited && (_parentUuid.size() > 0);
|
|
std::shared_ptr<Material> parent;
|
|
if (inherited) {
|
|
try {
|
|
parent = materialManager.getMaterial(_parentUuid);
|
|
}
|
|
catch (const MaterialNotFound&) {
|
|
inherited = false;
|
|
}
|
|
}
|
|
|
|
bool headerPrinted = false;
|
|
for (auto& itm : _physicalUuids) {
|
|
auto model = modelManager.getModel(itm);
|
|
if (!inherited || modelChanged(*parent, *model)) {
|
|
if (!headerPrinted) {
|
|
stream << "Models:\n";
|
|
headerPrinted = true;
|
|
}
|
|
stream << " " << MaterialValue::escapeString(model->getName()) << ":\n";
|
|
stream << " UUID: \"" << model->getUUID() << "\"\n";
|
|
for (const auto& it : *model) {
|
|
QString propertyName = it.first;
|
|
std::shared_ptr<MaterialProperty> property = getPhysicalProperty(propertyName);
|
|
std::shared_ptr<MaterialProperty> parentProperty;
|
|
try {
|
|
if (inherited) {
|
|
parentProperty = parent->getPhysicalProperty(propertyName);
|
|
}
|
|
}
|
|
catch (const PropertyNotFound&) {
|
|
Base::Console().log("Material::saveModels Property not found '%s'\n",
|
|
propertyName.toStdString().c_str());
|
|
}
|
|
|
|
if (!inherited || !parentProperty || (*property != *parentProperty)) {
|
|
if (!property->isNull()) {
|
|
stream << " " << *property << "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Material::saveAppearanceModels(QTextStream& stream, bool saveInherited) const
|
|
{
|
|
if (_appearance.empty()) {
|
|
return;
|
|
}
|
|
|
|
auto& modelManager = ModelManager::getManager();
|
|
auto& materialManager = MaterialManager::getManager();
|
|
|
|
bool inherited = saveInherited && (_parentUuid.size() > 0);
|
|
std::shared_ptr<Material> parent;
|
|
if (inherited) {
|
|
try {
|
|
parent = materialManager.getMaterial(_parentUuid);
|
|
}
|
|
catch (const MaterialNotFound&) {
|
|
inherited = false;
|
|
}
|
|
}
|
|
|
|
bool headerPrinted = false;
|
|
for (auto& itm : _appearanceUuids) {
|
|
auto model = modelManager.getModel(itm);
|
|
if (!inherited || modelAppearanceChanged(*parent, *model)) {
|
|
if (!headerPrinted) {
|
|
stream << "AppearanceModels:\n";
|
|
headerPrinted = true;
|
|
}
|
|
stream << " " << MaterialValue::escapeString(model->getName()) << ":\n";
|
|
stream << " UUID: \"" << model->getUUID() << "\"\n";
|
|
for (const auto& it : *model) {
|
|
QString propertyName = it.first;
|
|
std::shared_ptr<MaterialProperty> property = getAppearanceProperty(propertyName);
|
|
std::shared_ptr<MaterialProperty> parentProperty;
|
|
try {
|
|
if (inherited) {
|
|
parentProperty = parent->getAppearanceProperty(propertyName);
|
|
}
|
|
}
|
|
catch (const PropertyNotFound&) {
|
|
}
|
|
|
|
if (!inherited || !parentProperty || (*property != *parentProperty)) {
|
|
if (!property->isNull()) {
|
|
stream << " " << *property << "\n";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Material::newUuid()
|
|
{
|
|
_uuid = QUuid::createUuid().toString(QUuid::WithoutBraces);
|
|
}
|
|
|
|
QString Material::getModelByName(const QString& name) const
|
|
{
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
for (auto& it : _allUuids) {
|
|
try {
|
|
auto model = manager.getModel(it);
|
|
if (model->getName() == name) {
|
|
return it;
|
|
}
|
|
}
|
|
catch (ModelNotFound const&) {
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
void Material::save(QTextStream& stream, bool overwrite, bool saveAsCopy, bool saveInherited)
|
|
{
|
|
if (saveInherited && !saveAsCopy) {
|
|
// Check to see if we're an original or if we're already in the list of
|
|
// models
|
|
if (MaterialManager::getManager().exists(_uuid) && !overwrite) {
|
|
// Make a new version based on the current
|
|
setParentUUID(_uuid);
|
|
}
|
|
}
|
|
|
|
// Prevent self inheritance
|
|
if (_parentUuid == _uuid) {
|
|
_parentUuid = QString();
|
|
}
|
|
|
|
if (saveAsCopy) {
|
|
// Save it in the same format as the parent
|
|
if (_parentUuid.isEmpty()) {
|
|
saveInherited = false;
|
|
}
|
|
else {
|
|
saveInherited = true;
|
|
}
|
|
}
|
|
else {
|
|
if (!overwrite) {
|
|
// Creating a new derived model when overwriting sets itself as a
|
|
// parent, that will no longer exist because it's been overwritten
|
|
newUuid();
|
|
}
|
|
}
|
|
|
|
stream << "---\n";
|
|
stream << "# File created by " << QString::fromStdString(App::Application::Config()["ExeName"])
|
|
<< " " << QString::fromStdString(App::Application::Config()["ExeVersion"])
|
|
<< " Revision: " << QString::fromStdString(App::Application::Config()["BuildRevision"])
|
|
<< "\n";
|
|
saveGeneral(stream);
|
|
if (saveInherited) {
|
|
saveInherits(stream);
|
|
}
|
|
saveModels(stream, saveInherited);
|
|
saveAppearanceModels(stream, saveInherited);
|
|
|
|
setOldFormat(false);
|
|
}
|
|
|
|
Material& Material::operator=(const Material& other)
|
|
{
|
|
if (this == &other) {
|
|
return *this;
|
|
}
|
|
|
|
_library = other._library;
|
|
_directory = other._directory;
|
|
_filename = other._filename;
|
|
_uuid = other._uuid;
|
|
_name = other._name;
|
|
_author = other._author;
|
|
_license = other._license;
|
|
_parentUuid = other._parentUuid;
|
|
_description = other._description;
|
|
_url = other._url;
|
|
_reference = other._reference;
|
|
_dereferenced = other._dereferenced;
|
|
_oldFormat = other._oldFormat;
|
|
_editState = other._editState;
|
|
|
|
_tags.clear();
|
|
for (auto& it : other._tags) {
|
|
_tags.insert(it);
|
|
}
|
|
_physicalUuids.clear();
|
|
for (auto& it : other._physicalUuids) {
|
|
_physicalUuids.insert(it);
|
|
}
|
|
_appearanceUuids.clear();
|
|
for (auto& it : other._appearanceUuids) {
|
|
_appearanceUuids.insert(it);
|
|
}
|
|
_allUuids.clear();
|
|
for (auto& it : other._allUuids) {
|
|
_allUuids.insert(it);
|
|
}
|
|
|
|
// Create copies of the properties rather than modify the originals
|
|
_physical.clear();
|
|
for (auto& it : other._physical) {
|
|
MaterialProperty prop(it.second);
|
|
_physical[it.first] = std::make_shared<MaterialProperty>(prop);
|
|
}
|
|
_appearance.clear();
|
|
for (auto& it : other._appearance) {
|
|
MaterialProperty prop(it.second);
|
|
_appearance[it.first] = std::make_shared<MaterialProperty>(prop);
|
|
}
|
|
_legacy.clear();
|
|
for (auto& it : other._legacy) {
|
|
_legacy[it.first] = it.second;
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
Material& Material::operator=(const App::Material& other)
|
|
{
|
|
if (!hasAppearanceModel(ModelUUIDs::ModelUUID_Rendering_Basic)) {
|
|
addAppearance(ModelUUIDs::ModelUUID_Rendering_Basic);
|
|
}
|
|
|
|
getAppearanceProperty(QStringLiteral("AmbientColor"))->setColor(other.ambientColor);
|
|
getAppearanceProperty(QStringLiteral("DiffuseColor"))->setColor(other.diffuseColor);
|
|
getAppearanceProperty(QStringLiteral("SpecularColor"))->setColor(other.specularColor);
|
|
getAppearanceProperty(QStringLiteral("EmissiveColor"))->setColor(other.emissiveColor);
|
|
getAppearanceProperty(QStringLiteral("Shininess"))->setFloat(other.shininess);
|
|
getAppearanceProperty(QStringLiteral("Transparency"))->setFloat(other.transparency);
|
|
|
|
if (!other.image.empty() || !other.imagePath.empty()) {
|
|
if (!hasAppearanceModel(ModelUUIDs::ModelUUID_Rendering_Texture)) {
|
|
addAppearance(ModelUUIDs::ModelUUID_Rendering_Texture);
|
|
}
|
|
|
|
getAppearanceProperty(QStringLiteral("TextureImage"))->setString(other.image);
|
|
getAppearanceProperty(QStringLiteral("TexturePath"))->setString(other.imagePath);
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
/*
|
|
* Normalize models by removing any inherited models
|
|
*/
|
|
QStringList Material::normalizeModels(const QStringList& models)
|
|
{
|
|
QStringList normalized;
|
|
|
|
auto& manager = ModelManager::getManager();
|
|
|
|
for (auto& uuid : models) {
|
|
auto model = manager.getModel(uuid);
|
|
|
|
bool found = false;
|
|
for (auto& childUuid : models) {
|
|
if (uuid != childUuid) {
|
|
auto childModel = manager.getModel(childUuid);
|
|
if (childModel->inherits(childUuid)) {
|
|
// We're an inherited model
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (!found) {
|
|
normalized << uuid;
|
|
}
|
|
}
|
|
|
|
return normalized;
|
|
}
|
|
|
|
/*
|
|
* Set or change the base material for the current material, updating the
|
|
* properties as required.
|
|
*/
|
|
void Material::updateInheritance([[maybe_unused]] const QString& parent)
|
|
{}
|
|
|
|
/*
|
|
* Return a list of models that are defined in the parent material but not in
|
|
* this one
|
|
*/
|
|
QStringList Material::inheritedMissingModels(const Material& parent) const
|
|
{
|
|
QStringList missing;
|
|
for (auto& uuid : parent._allUuids) {
|
|
if (!hasModel(uuid)) {
|
|
missing << uuid;
|
|
}
|
|
}
|
|
|
|
return normalizeModels(missing);
|
|
}
|
|
|
|
/*
|
|
* Return a list of models that are defined in this model but not the parent
|
|
*/
|
|
QStringList Material::inheritedAddedModels(const Material& parent) const
|
|
{
|
|
QStringList added;
|
|
for (auto& uuid : _allUuids) {
|
|
if (!parent.hasModel(uuid)) {
|
|
added << uuid;
|
|
}
|
|
}
|
|
|
|
return normalizeModels(added);
|
|
}
|
|
|
|
/*
|
|
* Return a list of properties that have different values from the parent
|
|
* material
|
|
*/
|
|
void Material::inheritedPropertyDiff([[maybe_unused]] const QString& parent)
|
|
{}
|
|
|
|
/*
|
|
* Return an App::Material object describing the materials appearance, or DEFAULT if
|
|
* undefined.
|
|
*/
|
|
App::Material Material::getMaterialAppearance() const
|
|
{
|
|
App::Material material(App::Material::DEFAULT);
|
|
|
|
bool custom = false;
|
|
if (hasAppearanceProperty(QStringLiteral("AmbientColor"))) {
|
|
material.ambientColor = getAppearanceProperty(QStringLiteral("AmbientColor"))->getColor();
|
|
custom = true;
|
|
}
|
|
if (hasAppearanceProperty(QStringLiteral("DiffuseColor"))) {
|
|
material.diffuseColor = getAppearanceProperty(QStringLiteral("DiffuseColor"))->getColor();
|
|
custom = true;
|
|
}
|
|
if (hasAppearanceProperty(QStringLiteral("SpecularColor"))) {
|
|
material.specularColor = getAppearanceProperty(QStringLiteral("SpecularColor"))->getColor();
|
|
custom = true;
|
|
}
|
|
if (hasAppearanceProperty(QStringLiteral("EmissiveColor"))) {
|
|
material.emissiveColor = getAppearanceProperty(QStringLiteral("EmissiveColor"))->getColor();
|
|
custom = true;
|
|
}
|
|
if (hasAppearanceProperty(QStringLiteral("Shininess"))) {
|
|
material.shininess = getAppearanceProperty(QStringLiteral("Shininess"))->getFloat();
|
|
custom = true;
|
|
}
|
|
if (hasAppearanceProperty(QStringLiteral("Transparency"))) {
|
|
material.transparency = getAppearanceProperty(QStringLiteral("Transparency"))->getFloat();
|
|
custom = true;
|
|
}
|
|
if (hasAppearanceProperty(QStringLiteral("TextureImage"))) {
|
|
auto property = getAppearanceProperty(QStringLiteral("TextureImage"));
|
|
if (!property->isNull()) {
|
|
Base::Console().log("Has 'TextureImage'\n");
|
|
material.image = property->getString().toStdString();
|
|
}
|
|
|
|
custom = true;
|
|
}
|
|
else if (hasAppearanceProperty(QStringLiteral("TexturePath"))) {
|
|
auto property = getAppearanceProperty(QStringLiteral("TexturePath"));
|
|
if (!property->isNull()) {
|
|
Base::Console().log("Has 'TexturePath'\n");
|
|
material.imagePath = property->getString().toStdString();
|
|
}
|
|
|
|
custom = true;
|
|
}
|
|
|
|
if (custom) {
|
|
material.setType(App::Material::USER_DEFINED);
|
|
material.uuid = getUUID().toStdString();
|
|
}
|
|
|
|
return material;
|
|
}
|
|
|
|
void Material::validate(Material& other) const
|
|
{
|
|
|
|
try {
|
|
_library->validate(*other._library);
|
|
}
|
|
catch (const InvalidLibrary& e) {
|
|
throw InvalidMaterial(e.what());
|
|
}
|
|
|
|
if (_directory != other._directory) {
|
|
throw InvalidMaterial("Model directories don't match");
|
|
}
|
|
if (!other._filename.isEmpty()) {
|
|
throw InvalidMaterial("Remote filename is not empty");
|
|
}
|
|
if (_uuid != other._uuid) {
|
|
throw InvalidMaterial("Model UUIDs don't match");
|
|
}
|
|
if (_name != other._name) {
|
|
throw InvalidMaterial("Model names don't match");
|
|
}
|
|
if (_author != other._author) {
|
|
throw InvalidMaterial("Model authors don't match");
|
|
}
|
|
if (_license != other._license) {
|
|
throw InvalidMaterial("Model licenses don't match");
|
|
}
|
|
if (_parentUuid != other._parentUuid) {
|
|
throw InvalidMaterial("Model parents don't match");
|
|
}
|
|
if (_description != other._description) {
|
|
throw InvalidMaterial("Model descriptions don't match");
|
|
}
|
|
if (_url != other._url) {
|
|
throw InvalidMaterial("Model URLs don't match");
|
|
}
|
|
if (_reference != other._reference) {
|
|
throw InvalidMaterial("Model references don't match");
|
|
}
|
|
|
|
if (_tags.size() != other._tags.size()) {
|
|
Base::Console().log("Local tags count %d\n", _tags.size());
|
|
Base::Console().log("Remote tags count %d\n", other._tags.size());
|
|
throw InvalidMaterial("Material tags counts don't match");
|
|
}
|
|
if (!other._tags.contains(_tags)) {
|
|
throw InvalidMaterial("Material tags don't match");
|
|
}
|
|
|
|
if (_physicalUuids.size() != other._physicalUuids.size()) {
|
|
Base::Console().log("Local physical model count %d\n", _physicalUuids.size());
|
|
Base::Console().log("Remote physical model count %d\n", other._physicalUuids.size());
|
|
throw InvalidMaterial("Material physical model counts don't match");
|
|
}
|
|
if (!other._physicalUuids.contains(_physicalUuids)) {
|
|
throw InvalidMaterial("Material physical models don't match");
|
|
}
|
|
|
|
if (_physicalUuids.size() != other._physicalUuids.size()) {
|
|
Base::Console().log("Local appearance model count %d\n", _physicalUuids.size());
|
|
Base::Console().log("Remote appearance model count %d\n", other._physicalUuids.size());
|
|
throw InvalidMaterial("Material appearance model counts don't match");
|
|
}
|
|
if (!other._physicalUuids.contains(_physicalUuids)) {
|
|
throw InvalidMaterial("Material appearance models don't match");
|
|
}
|
|
|
|
if (_allUuids.size() != other._allUuids.size()) {
|
|
Base::Console().log("Local model count %d\n", _allUuids.size());
|
|
Base::Console().log("Remote model count %d\n", other._allUuids.size());
|
|
throw InvalidMaterial("Material model counts don't match");
|
|
}
|
|
if (!other._allUuids.contains(_allUuids)) {
|
|
throw InvalidMaterial("Material models don't match");
|
|
}
|
|
|
|
// Need to compare properties
|
|
if (_physical.size() != other._physical.size()) {
|
|
throw InvalidMaterial("Material physical property counts don't match");
|
|
}
|
|
for (auto& property : _physical) {
|
|
auto& remote = other._physical[property.first];
|
|
property.second->validate(*remote);
|
|
}
|
|
|
|
if (_appearance.size() != other._appearance.size()) {
|
|
throw InvalidMaterial("Material appearance property counts don't match");
|
|
}
|
|
for (auto& property : _appearance) {
|
|
auto& remote = other._appearance[property.first];
|
|
property.second->validate(*remote);
|
|
}
|
|
}
|