1
/***************************************************************************
2
* Copyright (c) 2023 David Carter <dcarter@david.carter.ca> *
4
* This file is part of FreeCAD. *
6
* FreeCAD is free software: you can redistribute it and/or modify it *
7
* under the terms of the GNU Lesser General Public License as *
8
* published by the Free Software Foundation, either version 2.1 of the *
9
* License, or (at your option) any later version. *
11
* FreeCAD is distributed in the hope that it will be useful, but *
12
* WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14
* Lesser General Public License for more details. *
16
* You should have received a copy of the GNU Lesser General Public *
17
* License along with FreeCAD. If not, see *
18
* <https://www.gnu.org/licenses/>. *
20
**************************************************************************/
22
#include "PreCompiled.h"
28
#include <QDirIterator>
29
#include <QMutexLocker>
31
#include <App/Application.h>
32
#include <App/Material.h>
34
#include "Exceptions.h"
35
#include "MaterialConfigLoader.h"
36
#include "MaterialLoader.h"
37
#include "MaterialManager.h"
38
#include "ModelManager.h"
39
#include "ModelUuids.h"
42
using namespace Materials;
44
/* TRANSLATOR Material::Materials */
46
std::shared_ptr<std::list<std::shared_ptr<MaterialLibrary>>> MaterialManager::_libraryList =
48
std::shared_ptr<std::map<QString, std::shared_ptr<Material>>> MaterialManager::_materialMap =
50
QMutex MaterialManager::_mutex;
52
TYPESYSTEM_SOURCE(Materials::MaterialManager, Base::BaseClass)
54
MaterialManager::MaterialManager()
56
// TODO: Add a mutex or similar
60
void MaterialManager::initLibraries()
62
QMutexLocker locker(&_mutex);
64
if (_materialMap == nullptr) {
65
// Load the models first
66
auto manager = std::make_unique<ModelManager>();
69
_materialMap = std::make_shared<std::map<QString, std::shared_ptr<Material>>>();
71
if (_libraryList == nullptr) {
72
_libraryList = std::make_shared<std::list<std::shared_ptr<MaterialLibrary>>>();
76
MaterialLoader loader(_materialMap, _libraryList);
80
void MaterialManager::cleanup()
82
QMutexLocker locker(&_mutex);
85
_libraryList->clear();
86
_libraryList = nullptr;
90
for (auto& it : *_materialMap) {
91
// This is needed to resolve cyclic dependencies
92
it.second->setLibrary(nullptr);
94
_materialMap->clear();
95
_materialMap = nullptr;
99
void MaterialManager::refresh()
101
// This is very expensive and can be improved using observers?
106
void MaterialManager::saveMaterial(const std::shared_ptr<MaterialLibrary>& library,
107
const std::shared_ptr<Material>& material,
111
bool saveInherited) const
113
auto newMaterial = library->saveMaterial(material, path, overwrite, saveAsCopy, saveInherited);
114
(*_materialMap)[newMaterial->getUUID()] = newMaterial;
117
bool MaterialManager::isMaterial(const fs::path& p) const
119
if (!fs::is_regular_file(p)) {
122
// check file extension
123
if (p.extension() == ".FCMat") {
129
bool MaterialManager::isMaterial(const QFileInfo& file) const
131
if (!file.isFile()) {
134
// check file extension
135
if (file.suffix() == QString::fromStdString("FCMat")) {
141
std::shared_ptr<App::Material> MaterialManager::defaultAppearance()
143
ParameterGrp::handle hGrp =
144
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
146
auto getColor = [hGrp](const char* parameter, App::Color& color) {
147
uint32_t packed = color.getPackedRGB();
148
packed = hGrp->GetUnsigned(parameter, packed);
149
color.setPackedRGB(packed);
150
color.a = 1.0; // The default color sets fully transparent, not opaque
152
auto intRandom = [](int min, int max) -> int {
153
static std::mt19937 generator;
154
std::uniform_int_distribution<int> distribution(min, max);
155
return distribution(generator);
158
App::Material mat(App::Material::DEFAULT);
159
bool randomColor = hGrp->GetBool("RandomColor", false);
162
float red = static_cast<float>(intRandom(0, 255)) / 255.0F;
163
float green = static_cast<float>(intRandom(0, 255)) / 255.0F;
164
float blue = static_cast<float>(intRandom(0, 255)) / 255.0F;
165
mat.diffuseColor = App::Color(red, green, blue, 1.0);
168
getColor("DefaultShapeColor", mat.diffuseColor);
171
getColor("DefaultAmbientColor", mat.ambientColor);
172
getColor("DefaultEmissiveColor", mat.emissiveColor);
173
getColor("DefaultSpecularColor", mat.specularColor);
175
long initialTransparency = hGrp->GetInt("DefaultShapeTransparency", 0);
176
long initialShininess = hGrp->GetInt("DefaultShapeShininess", 90);
177
mat.shininess = ((float)initialShininess / 100.0F);
178
mat.transparency = ((float)initialTransparency / 100.0F);
180
return std::make_shared<App::Material>(mat);
183
std::shared_ptr<Material> MaterialManager::defaultMaterial()
185
MaterialManager manager;
187
auto mat = defaultAppearance();
188
auto material = manager.getMaterial(defaultMaterialUUID());
190
material = manager.getMaterial(QLatin1String("7f9fd73b-50c9-41d8-b7b2-575a030c1eeb"));
192
if (material->hasAppearanceModel(ModelUUIDs::ModelUUID_Rendering_Basic)) {
193
material->getAppearanceProperty(QString::fromLatin1("DiffuseColor"))
194
->setColor(mat->diffuseColor);
195
material->getAppearanceProperty(QString::fromLatin1("AmbientColor"))
196
->setColor(mat->ambientColor);
197
material->getAppearanceProperty(QString::fromLatin1("EmissiveColor"))
198
->setColor(mat->emissiveColor);
199
material->getAppearanceProperty(QString::fromLatin1("SpecularColor"))
200
->setColor(mat->specularColor);
201
material->getAppearanceProperty(QString::fromLatin1("Transparency"))
202
->setFloat(mat->transparency);
203
material->getAppearanceProperty(QString::fromLatin1("Shininess"))
204
->setFloat(mat->shininess);
210
QString MaterialManager::defaultMaterialUUID()
212
// Make this a preference
213
auto param = App::GetApplication().GetParameterGroupByPath(
214
"User parameter:BaseApp/Preferences/Mod/Material");
215
auto uuid = param->GetASCII("DefaultMaterial", "7f9fd73b-50c9-41d8-b7b2-575a030c1eeb");
216
return QString::fromStdString(uuid);
219
std::shared_ptr<Material> MaterialManager::getMaterial(const QString& uuid) const
222
return _materialMap->at(uuid);
224
catch (std::out_of_range&) {
225
throw MaterialNotFound();
229
std::shared_ptr<Material> MaterialManager::getMaterial(const App::Material& material)
231
MaterialManager manager;
233
return manager.getMaterial(QString::fromStdString(material.uuid));
236
std::shared_ptr<Material> MaterialManager::getMaterialByPath(const QString& path) const
238
QString cleanPath = QDir::cleanPath(path);
240
for (auto& library : *_libraryList) {
241
if (cleanPath.startsWith(library->getDirectory())) {
243
return library->getMaterialByPath(cleanPath);
245
catch (const MaterialNotFound&) {
248
// See if it's a new file saved by the old editor
250
QMutexLocker locker(&_mutex);
252
if (MaterialConfigLoader::isConfigStyle(path)) {
253
auto material = MaterialConfigLoader::getMaterialFromPath(library, path);
255
(*_materialMap)[material->getUUID()] = library->addMaterial(material, path);
264
// Older workbenches may try files outside the context of a library
266
QMutexLocker locker(&_mutex);
268
if (MaterialConfigLoader::isConfigStyle(path)) {
269
auto material = MaterialConfigLoader::getMaterialFromPath(nullptr, path);
275
throw MaterialNotFound();
278
std::shared_ptr<Material> MaterialManager::getMaterialByPath(const QString& path,
279
const QString& lib) const
281
auto library = getLibrary(lib); // May throw LibraryNotFound
282
return library->getMaterialByPath(path); // May throw MaterialNotFound
285
bool MaterialManager::exists(const QString& uuid) const
288
auto material = getMaterial(uuid);
293
catch (const MaterialNotFound&) {
299
std::shared_ptr<Material>
300
MaterialManager::getParent(const std::shared_ptr<Material>& material) const
302
if (material->getParentUUID().isEmpty()) {
303
throw MaterialNotFound();
306
return getMaterial(material->getParentUUID());
309
bool MaterialManager::exists(const std::shared_ptr<MaterialLibrary>& library,
310
const QString& uuid) const
313
auto material = getMaterial(uuid);
315
return (*material->getLibrary() == *library);
318
catch (const MaterialNotFound&) {
324
std::shared_ptr<MaterialLibrary> MaterialManager::getLibrary(const QString& name) const
326
for (auto& library : *_libraryList) {
327
if (library->getName() == name) {
332
throw LibraryNotFound();
335
std::shared_ptr<std::list<std::shared_ptr<MaterialLibrary>>>
336
MaterialManager::getMaterialLibraries() const
338
if (_libraryList == nullptr) {
339
if (_materialMap == nullptr) {
340
_materialMap = std::make_shared<std::map<QString, std::shared_ptr<Material>>>();
342
_libraryList = std::make_shared<std::list<std::shared_ptr<MaterialLibrary>>>();
344
// Load the libraries
345
MaterialLoader loader(_materialMap, _libraryList);
350
std::shared_ptr<std::list<QString>>
351
MaterialManager::getMaterialFolders(const std::shared_ptr<MaterialLibrary>& library) const
353
return MaterialLoader::getMaterialFolders(*library);
356
std::shared_ptr<std::map<QString, std::shared_ptr<Material>>>
357
MaterialManager::materialsWithModel(const QString& uuid) const
359
std::shared_ptr<std::map<QString, std::shared_ptr<Material>>> dict =
360
std::make_shared<std::map<QString, std::shared_ptr<Material>>>();
362
for (auto& it : *_materialMap) {
363
QString key = it.first;
364
auto material = it.second;
366
if (material->hasModel(uuid)) {
367
(*dict)[key] = material;
374
std::shared_ptr<std::map<QString, std::shared_ptr<Material>>>
375
MaterialManager::materialsWithModelComplete(const QString& uuid) const
377
std::shared_ptr<std::map<QString, std::shared_ptr<Material>>> dict =
378
std::make_shared<std::map<QString, std::shared_ptr<Material>>>();
380
for (auto& it : *_materialMap) {
381
QString key = it.first;
382
auto material = it.second;
384
if (material->isModelComplete(uuid)) {
385
(*dict)[key] = material;
392
void MaterialManager::dereference() const
394
// First clear the inheritences
395
for (auto& it : *_materialMap) {
396
auto material = it.second;
397
material->clearDereferenced();
398
material->clearInherited();
401
// Run the dereference again
402
for (auto& it : *_materialMap) {
403
dereference(it.second);
407
void MaterialManager::dereference(std::shared_ptr<Material> material) const
409
MaterialLoader::dereference(_materialMap, material);