// 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 #include #include #include "Model.h" #include "ModelLoader.h" #include "ModelManager.h" using namespace Materials; ModelEntry::ModelEntry(const std::shared_ptr& library, const QString& baseName, const QString& modelName, const QString& dir, const QString& modelUuid, const YAML::Node& modelData) : _library(library) , _base(baseName) , _name(modelName) , _directory(dir) , _uuid(modelUuid) , _model(modelData) , _dereferenced(false) {} std::unique_ptr>> ModelLoader::_modelEntryMap = nullptr; ModelLoader::ModelLoader(std::shared_ptr>> modelMap, std::shared_ptr>> libraryList) : _modelMap(modelMap) , _libraryList(libraryList) { loadLibraries(); } void ModelLoader::addLibrary(std::shared_ptr model) { _libraryList->push_back(model); } const QString ModelLoader::getUUIDFromPath(const QString& path) { QFile file(path); if (!file.exists()) { throw ModelNotFound(); } try { Base::FileInfo fi(path.toStdString()); Base::ifstream str(fi); YAML::Node yamlroot = YAML::Load(str); std::string base = "Model"; if (yamlroot["AppearanceModel"]) { base = "AppearanceModel"; } const QString uuid = QString::fromStdString(yamlroot[base]["UUID"].as()); return uuid; } catch (YAML::Exception&) { throw ModelNotFound(); } } std::shared_ptr ModelLoader::getModelFromPath(std::shared_ptr library, const QString& path) const { QFile file(path); if (!file.exists()) { throw ModelNotFound(); } YAML::Node yamlroot; std::string base = "Model"; std::string uuid; std::string name; try { Base::FileInfo fi(path.toStdString()); Base::ifstream str(fi); yamlroot = YAML::Load(str); if (yamlroot["AppearanceModel"]) { base = "AppearanceModel"; } uuid = yamlroot[base]["UUID"].as(); name = yamlroot[base]["Name"].as(); } catch (YAML::Exception const&) { throw InvalidModel(); } auto localLibrary = std::static_pointer_cast(library); std::shared_ptr model = std::make_shared(localLibrary, QString::fromStdString(base), QString::fromStdString(name), path, QString::fromStdString(uuid), yamlroot); return model; } void ModelLoader::showYaml(const YAML::Node& yaml) const { std::stringstream out; out << yaml; std::string logData = out.str(); Base::Console().log("%s\n", logData.c_str()); } void ModelLoader::dereference(const QString& uuid, std::shared_ptr parent, std::shared_ptr child, std::map, QString>* inheritances) { auto parentPtr = parent->getModelPtr(); auto parentBase = parent->getBase().toStdString(); auto childYaml = child->getModel(); auto childBase = child->getBase().toStdString(); std::set exclude; exclude.insert(QStringLiteral("Name")); exclude.insert(QStringLiteral("UUID")); exclude.insert(QStringLiteral("URL")); exclude.insert(QStringLiteral("Description")); exclude.insert(QStringLiteral("DOI")); exclude.insert(QStringLiteral("Inherits")); auto parentProperties = (*parentPtr)[parentBase]; auto childProperties = childYaml[childBase]; for (auto it = childProperties.begin(); it != childProperties.end(); it++) { std::string name = it->first.as(); if (!exclude.contains(QString::fromStdString(name))) { // showYaml(it->second); if (!parentProperties[name]) { parentProperties[name] = it->second; // parentProperties[name]["Inherits"] = childYaml[childBase]["UUID"]; (*inheritances)[std::pair(uuid, QString::fromStdString(name))] = yamlValue(childYaml[childBase], "UUID", ""); } } } // showYaml(*parentPtr); } void ModelLoader::dereference(std::shared_ptr model, std::map, QString>* inheritances) { // Avoid recursion if (model->getDereferenced()) { return; } auto yamlModel = model->getModel(); auto base = model->getBase().toStdString(); if (yamlModel[base]["Inherits"]) { auto inherits = yamlModel[base]["Inherits"]; for (auto it = inherits.begin(); it != inherits.end(); it++) { QString nodeName = QString::fromStdString((*it)["UUID"].as()); // This requires that all models have already been loaded undereferenced try { std::shared_ptr child = (*_modelEntryMap)[nodeName]; dereference(model->getUUID(), model, child, inheritances); } catch (const std::out_of_range&) { Base::Console().log("Unable to find '%s' in model map\n", nodeName.toStdString().c_str()); } } } model->markDereferenced(); } QString ModelLoader::yamlValue(const YAML::Node& node, const std::string& key, const std::string& defaultValue) { if (node[key]) { return QString::fromStdString(node[key].as()); } return QString::fromStdString(defaultValue); } void ModelLoader::addToTree(std::shared_ptr model, std::map, QString>* inheritances) { std::set exclude; exclude.insert(QStringLiteral("Name")); exclude.insert(QStringLiteral("UUID")); exclude.insert(QStringLiteral("URL")); exclude.insert(QStringLiteral("Description")); exclude.insert(QStringLiteral("DOI")); exclude.insert(QStringLiteral("Inherits")); auto yamlModel = model->getModel(); if (!model->getLibrary()->isLocal()) { throw InvalidLibrary(); } auto library = model->getLibrary(); auto base = model->getBase().toStdString(); auto name = model->getName(); auto directory = model->getDirectory(); auto uuid = model->getUUID(); QString description = yamlValue(yamlModel[base], "Description", ""); QString url = yamlValue(yamlModel[base], "URL", ""); QString doi = yamlValue(yamlModel[base], "DOI", ""); Model::ModelType type = (base == "Model") ? Model::ModelType_Physical : Model::ModelType_Appearance; Model finalModel(library, type, name, directory, uuid, description, url, doi); // Add inheritance list if (yamlModel[base]["Inherits"]) { auto inherits = yamlModel[base]["Inherits"]; for (auto it = inherits.begin(); it != inherits.end(); it++) { QString nodeName = QString::fromStdString((*it)["UUID"].as()); finalModel.addInheritance(nodeName); } } // Add property list auto yamlProperties = yamlModel[base]; for (auto it = yamlProperties.begin(); it != yamlProperties.end(); it++) { std::string propName = it->first.as(); if (!exclude.contains(QString::fromStdString(propName))) { // showYaml(it->second); auto yamlProp = yamlProperties[propName]; auto propDisplayName = yamlValue(yamlProp, "DisplayName", ""); auto propType = yamlValue(yamlProp, "Type", ""); auto propUnits = yamlValue(yamlProp, "Units", ""); auto propURL = yamlValue(yamlProp, "URL", ""); auto propDescription = yamlValue(yamlProp, "Description", ""); // auto inherits = yamlValue(yamlProp, "Inherits", ""); ModelProperty property(QString::fromStdString(propName), propDisplayName, propType, propUnits, propURL, propDescription); if (propType == QStringLiteral("2DArray") || propType == QStringLiteral("3DArray")) { // Base::Console().Log("Reading columns\n"); // Read the columns auto cols = yamlProp["Columns"]; for (const auto& col : cols) { std::string colName = col.first.as(); // Base::Console().Log("\tColumns '%s'\n", colName.c_str()); auto colProp = cols[colName]; auto colPropDisplayName = yamlValue(colProp, "DisplayName", ""); auto colPropType = yamlValue(colProp, "Type", ""); auto colPropUnits = yamlValue(colProp, "Units", ""); auto colPropURL = yamlValue(colProp, "URL", ""); auto colPropDescription = yamlValue(colProp, "Description", ""); ModelProperty colProperty(QString::fromStdString(colName), colPropDisplayName, colPropType, colPropUnits, colPropURL, colPropDescription); property.addColumn(colProperty); } } auto key = std::pair(uuid, QString::fromStdString(propName)); if (inheritances->contains(key)) { property.setInheritance((*inheritances)[key]); } finalModel.addProperty(property); } } (*_modelMap)[uuid] = library->addModel(finalModel, directory); } void ModelLoader::loadLibrary(std::shared_ptr library) { if (_modelEntryMap == nullptr) { _modelEntryMap = std::make_unique>>(); } QDirIterator it(library->getDirectory(), QDirIterator::Subdirectories); while (it.hasNext()) { auto pathname = it.next(); QFileInfo file(pathname); if (file.isFile()) { if (file.suffix().toStdString() == "yml") { try { auto model = getModelFromPath(library, file.canonicalFilePath()); (*_modelEntryMap)[model->getUUID()] = model; // showYaml(model->getModel()); } catch (InvalidModel const&) { Base::Console().log("Invalid model '%s'\n", pathname.toStdString().c_str()); } } } } std::map, QString> inheritances; for (auto it = _modelEntryMap->begin(); it != _modelEntryMap->end(); it++) { dereference(it->second, &inheritances); } for (auto it = _modelEntryMap->begin(); it != _modelEntryMap->end(); it++) { addToTree(it->second, &inheritances); } } void ModelLoader::loadLibraries() { getModelLibraries(); if (_libraryList) { for (auto it = _libraryList->begin(); it != _libraryList->end(); it++) { loadLibrary(*it); } } } void ModelLoader::getModelLibraries() { auto param = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Material/Resources"); bool useBuiltInMaterials = param->GetBool("UseBuiltInMaterials", true); bool useMatFromModules = param->GetBool("UseMaterialsFromWorkbenches", true); bool useMatFromConfigDir = param->GetBool("UseMaterialsFromConfigDir", true); bool useMatFromCustomDir = param->GetBool("UseMaterialsFromCustomDir", true); if (useBuiltInMaterials) { QString resourceDir = QString::fromStdString(App::Application::getResourceDir() + "/Mod/Material/Resources/Models"); auto libData = std::make_shared(QStringLiteral("System"), resourceDir, QStringLiteral(":/icons/freecad.svg")); _libraryList->push_back(libData); } if (useMatFromModules) { auto moduleParam = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Material/Resources/Modules"); for (auto& group : moduleParam->GetGroups()) { // auto module = moduleParam->GetGroup(group->GetGroupName()); auto moduleName = QString::fromStdString(group->GetGroupName()); auto modelDir = QString::fromStdString(group->GetASCII("ModuleModelDir", "")); auto modelIcon = QString::fromStdString(group->GetASCII("ModuleIcon", "")); if (modelDir.length() > 0) { QDir dir(modelDir); if (dir.exists()) { auto libData = std::make_shared(moduleName, modelDir, modelIcon); _libraryList->push_back(libData); } } } } if (useMatFromConfigDir) { QString resourceDir = QString::fromStdString(App::Application::getUserAppDataDir() + "/Models"); if (!resourceDir.isEmpty()) { QDir materialDir(resourceDir); if (materialDir.exists()) { auto libData = std::make_shared( QStringLiteral("User"), resourceDir, QStringLiteral(":/icons/preferences-general.svg")); _libraryList->push_back(libData); } } } if (useMatFromCustomDir) { QString resourceDir = QString::fromStdString(param->GetASCII("CustomMaterialsDir", "")); if (!resourceDir.isEmpty()) { QDir materialDir(resourceDir); if (materialDir.exists()) { auto libData = std::make_shared(QStringLiteral("Custom"), resourceDir, QStringLiteral(":/icons/user.svg")); _libraryList->push_back(libData); } } } }