FreeCAD / src /Mod /Material /App /Materials.cpp
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
// 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);
}
}