/*************************************************************************** * 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 "PreCompiled.h" #ifndef _PreComp_ #include #endif #include #include #include #include #include #include "Exceptions.h" #include "MaterialConfigLoader.h" #include "MaterialLoader.h" #include "MaterialManager.h" #include "ModelManager.h" #include "ModelUuids.h" using namespace Materials; /* TRANSLATOR Material::Materials */ std::shared_ptr>> MaterialManager::_libraryList = nullptr; std::shared_ptr>> MaterialManager::_materialMap = nullptr; QMutex MaterialManager::_mutex; TYPESYSTEM_SOURCE(Materials::MaterialManager, Base::BaseClass) MaterialManager::MaterialManager() { // TODO: Add a mutex or similar initLibraries(); } void MaterialManager::initLibraries() { QMutexLocker locker(&_mutex); if (_materialMap == nullptr) { // Load the models first auto manager = std::make_unique(); Q_UNUSED(manager) _materialMap = std::make_shared>>(); if (_libraryList == nullptr) { _libraryList = std::make_shared>>(); } // Load the libraries MaterialLoader loader(_materialMap, _libraryList); } } void MaterialManager::cleanup() { QMutexLocker locker(&_mutex); if (_libraryList) { _libraryList->clear(); _libraryList = nullptr; } if (_materialMap) { for (auto& it : *_materialMap) { // This is needed to resolve cyclic dependencies it.second->setLibrary(nullptr); } _materialMap->clear(); _materialMap = nullptr; } } void MaterialManager::refresh() { // This is very expensive and can be improved using observers? cleanup(); initLibraries(); } void MaterialManager::saveMaterial(const std::shared_ptr& library, const std::shared_ptr& material, const QString& path, bool overwrite, bool saveAsCopy, bool saveInherited) const { auto newMaterial = library->saveMaterial(material, path, overwrite, saveAsCopy, saveInherited); (*_materialMap)[newMaterial->getUUID()] = newMaterial; } bool MaterialManager::isMaterial(const fs::path& p) const { if (!fs::is_regular_file(p)) { return false; } // check file extension if (p.extension() == ".FCMat") { return true; } return false; } bool MaterialManager::isMaterial(const QFileInfo& file) const { if (!file.isFile()) { return false; } // check file extension if (file.suffix() == QString::fromStdString("FCMat")) { return true; } return false; } std::shared_ptr MaterialManager::defaultAppearance() { ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); auto getColor = [hGrp](const char* parameter, App::Color& color) { uint32_t packed = color.getPackedRGB(); packed = hGrp->GetUnsigned(parameter, packed); color.setPackedRGB(packed); color.a = 1.0; // The default color sets fully transparent, not opaque }; auto intRandom = [](int min, int max) -> int { static std::mt19937 generator; std::uniform_int_distribution distribution(min, max); return distribution(generator); }; App::Material mat(App::Material::DEFAULT); bool randomColor = hGrp->GetBool("RandomColor", false); if (randomColor) { float red = static_cast(intRandom(0, 255)) / 255.0F; float green = static_cast(intRandom(0, 255)) / 255.0F; float blue = static_cast(intRandom(0, 255)) / 255.0F; mat.diffuseColor = App::Color(red, green, blue, 1.0); } else { getColor("DefaultShapeColor", mat.diffuseColor); } getColor("DefaultAmbientColor", mat.ambientColor); getColor("DefaultEmissiveColor", mat.emissiveColor); getColor("DefaultSpecularColor", mat.specularColor); long initialTransparency = hGrp->GetInt("DefaultShapeTransparency", 0); long initialShininess = hGrp->GetInt("DefaultShapeShininess", 90); mat.shininess = ((float)initialShininess / 100.0F); mat.transparency = ((float)initialTransparency / 100.0F); return std::make_shared(mat); } std::shared_ptr MaterialManager::defaultMaterial() { MaterialManager manager; auto mat = defaultAppearance(); auto material = manager.getMaterial(defaultMaterialUUID()); if (!material) { material = manager.getMaterial(QLatin1String("7f9fd73b-50c9-41d8-b7b2-575a030c1eeb")); } if (material->hasAppearanceModel(ModelUUIDs::ModelUUID_Rendering_Basic)) { material->getAppearanceProperty(QStringLiteral("DiffuseColor")) ->setColor(mat->diffuseColor); material->getAppearanceProperty(QStringLiteral("AmbientColor")) ->setColor(mat->ambientColor); material->getAppearanceProperty(QStringLiteral("EmissiveColor")) ->setColor(mat->emissiveColor); material->getAppearanceProperty(QStringLiteral("SpecularColor")) ->setColor(mat->specularColor); material->getAppearanceProperty(QStringLiteral("Transparency")) ->setFloat(mat->transparency); material->getAppearanceProperty(QStringLiteral("Shininess")) ->setFloat(mat->shininess); } return material; } QString MaterialManager::defaultMaterialUUID() { // Make this a preference auto param = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Material"); auto uuid = param->GetASCII("DefaultMaterial", "7f9fd73b-50c9-41d8-b7b2-575a030c1eeb"); return QString::fromStdString(uuid); } std::shared_ptr MaterialManager::getMaterial(const QString& uuid) const { try { return _materialMap->at(uuid); } catch (std::out_of_range&) { throw MaterialNotFound(); } } std::shared_ptr MaterialManager::getMaterial(const App::Material& material) { MaterialManager manager; return manager.getMaterial(QString::fromStdString(material.uuid)); } std::shared_ptr MaterialManager::getMaterialByPath(const QString& path) const { QString cleanPath = QDir::cleanPath(path); for (auto& library : *_libraryList) { if (cleanPath.startsWith(library->getDirectory())) { try { return library->getMaterialByPath(cleanPath); } catch (const MaterialNotFound&) { } // See if it's a new file saved by the old editor { QMutexLocker locker(&_mutex); if (MaterialConfigLoader::isConfigStyle(path)) { auto material = MaterialConfigLoader::getMaterialFromPath(library, path); if (material) { (*_materialMap)[material->getUUID()] = library->addMaterial(material, path); } return material; } } } } // Older workbenches may try files outside the context of a library { QMutexLocker locker(&_mutex); if (MaterialConfigLoader::isConfigStyle(path)) { auto material = MaterialConfigLoader::getMaterialFromPath(nullptr, path); return material; } } throw MaterialNotFound(); } std::shared_ptr MaterialManager::getMaterialByPath(const QString& path, const QString& lib) const { auto library = getLibrary(lib); // May throw LibraryNotFound return library->getMaterialByPath(path); // May throw MaterialNotFound } bool MaterialManager::exists(const QString& uuid) const { try { auto material = getMaterial(uuid); if (material) { return true; } } catch (const MaterialNotFound&) { } return false; } std::shared_ptr MaterialManager::getParent(const std::shared_ptr& material) const { if (material->getParentUUID().isEmpty()) { throw MaterialNotFound(); } return getMaterial(material->getParentUUID()); } bool MaterialManager::exists(const std::shared_ptr& library, const QString& uuid) const { try { auto material = getMaterial(uuid); if (material) { return (*material->getLibrary() == *library); } } catch (const MaterialNotFound&) { } return false; } std::shared_ptr MaterialManager::getLibrary(const QString& name) const { for (auto& library : *_libraryList) { if (library->getName() == name) { return library; } } throw LibraryNotFound(); } std::shared_ptr>> MaterialManager::getMaterialLibraries() const { if (_libraryList == nullptr) { if (_materialMap == nullptr) { _materialMap = std::make_shared>>(); } _libraryList = std::make_shared>>(); // Load the libraries MaterialLoader loader(_materialMap, _libraryList); } return _libraryList; } std::shared_ptr> MaterialManager::getMaterialFolders(const std::shared_ptr& library) const { return MaterialLoader::getMaterialFolders(*library); } std::shared_ptr>> MaterialManager::materialsWithModel(const QString& uuid) const { std::shared_ptr>> dict = std::make_shared>>(); for (auto& it : *_materialMap) { QString key = it.first; auto material = it.second; if (material->hasModel(uuid)) { (*dict)[key] = material; } } return dict; } std::shared_ptr>> MaterialManager::materialsWithModelComplete(const QString& uuid) const { std::shared_ptr>> dict = std::make_shared>>(); for (auto& it : *_materialMap) { QString key = it.first; auto material = it.second; if (material->isModelComplete(uuid)) { (*dict)[key] = material; } } return dict; } void MaterialManager::dereference() const { // First clear the inheritences for (auto& it : *_materialMap) { auto material = it.second; material->clearDereferenced(); material->clearInherited(); } // Run the dereference again for (auto& it : *_materialMap) { dereference(it.second); } } void MaterialManager::dereference(std::shared_ptr material) const { MaterialLoader::dereference(_materialMap, material); }