// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2023 David Carter * * * * 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 * * . * * * **************************************************************************/ #include #include #include #include #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::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& value) { if (value->getType() == MaterialValue::Array2D) { _valuePtr = std::make_shared(*(std::static_pointer_cast(value))); } else if (value->getType() == MaterialValue::Array3D) { _valuePtr = std::make_shared(*(std::static_pointer_cast(value))); } else { _valuePtr = std::make_shared(*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& 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 MaterialProperty::getMaterialValue() { return _valuePtr; } std::shared_ptr 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(); 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(); 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(); arrayPtr->setColumns(columns()); _valuePtr = arrayPtr; } else if (mappedType == MaterialValue::Array3D) { auto arrayPtr = std::make_shared(); // First column is third dimension arrayPtr->setColumns(columns() - 1); _valuePtr = arrayPtr; } else { _valuePtr = std::make_shared(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()) { // Ensure the units are set correctly auto quantity = value.value(); 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& 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& 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& 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(prop); } for (auto& it : other._appearance) { MaterialProperty prop(it.second); _appearance[it.first] = std::make_shared(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& 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(it.second); try { _physical[propertyName] = std::make_shared(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(it.second); _appearance[propertyName] = std::make_shared(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& value) { setPhysicalEditState(name); if (hasPhysicalProperty(name)) { _physical[name]->setValue(value); } } void Material::setPhysicalValue(const QString& name, const std::shared_ptr>& 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& value) { setAppearanceEditState(name); if (hasAppearanceProperty(name)) { _appearance[name]->setValue(value); } } void Material::setAppearanceValue(const QString& name, const std::shared_ptr>& 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& 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 Material::getPhysicalProperty(const QString& name) { try { return _physical.at(name); } catch (std::out_of_range const&) { throw PropertyNotFound(); } } std::shared_ptr Material::getPhysicalProperty(const QString& name) const { try { return _physical.at(name); } catch (std::out_of_range const&) { throw PropertyNotFound(); } } std::shared_ptr Material::getAppearanceProperty(const QString& name) { try { return _appearance.at(name); } catch (std::out_of_range const&) { throw PropertyNotFound(); } } std::shared_ptr Material::getAppearanceProperty(const QString& name) const { try { return _appearance.at(name); } catch (std::out_of_range const&) { throw PropertyNotFound(); } } std::shared_ptr Material::getProperty(const QString& name) { if (hasPhysicalProperty(name)) { return getPhysicalProperty(name); } if (hasAppearanceProperty(name)) { return getAppearanceProperty(name); } throw PropertyNotFound(); } std::shared_ptr 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>& propertyList, const QString& name) { try { return propertyList.at(name)->getValue(); } catch (std::out_of_range const&) { throw PropertyNotFound(); } } QString Material::getValueString(const std::map>& 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().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(); } 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(); } 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 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 property = getPhysicalProperty(propertyName); std::shared_ptr 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 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 property = getAppearanceProperty(propertyName); std::shared_ptr 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(prop); } _appearance.clear(); for (auto& it : other._appearance) { MaterialProperty prop(it.second); _appearance[it.first] = std::make_shared(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); } }