diff --git a/src/Mod/Material/App/CMakeLists.txt b/src/Mod/Material/App/CMakeLists.txt index bc976d7293..ce78d35f07 100644 --- a/src/Mod/Material/App/CMakeLists.txt +++ b/src/Mod/Material/App/CMakeLists.txt @@ -24,6 +24,12 @@ include_directories( ) link_directories(${YAML_CPP_LIBRARY_DIR}) +if(BUILD_MATERIAL_EXTERNAL) + include_directories( + ${CMAKE_SOURCE_DIR}/src/3rdParty/lru-cache/include + ) +endif(BUILD_MATERIAL_EXTERNAL) + set(Materials_LIBS FreeCADApp ) @@ -139,6 +145,8 @@ if(BUILD_MATERIAL_EXTERNAL) list(APPEND Materials_SRCS ExternalManager.cpp ExternalManager.h + ModelManagerExternal.cpp + ModelManagerExternal.h ) endif(BUILD_MATERIAL_EXTERNAL) diff --git a/src/Mod/Material/App/ExternalManager.cpp b/src/Mod/Material/App/ExternalManager.cpp index 22d3920e11..595e512e42 100644 --- a/src/Mod/Material/App/ExternalManager.cpp +++ b/src/Mod/Material/App/ExternalManager.cpp @@ -116,7 +116,7 @@ void ExternalManager::instantiate() Py::Callable managerClass(mod.getAttr(_className)); _managerObject = managerClass.apply(); - if (_managerObject.hasAttr("APIVersion")) { + if (!_managerObject.isNull() && _managerObject.hasAttr("APIVersion")) { _instantiated = true; } diff --git a/src/Mod/Material/App/Library.h b/src/Mod/Material/App/Library.h index 5d773218d5..a46f57f34d 100644 --- a/src/Mod/Material/App/Library.h +++ b/src/Mod/Material/App/Library.h @@ -38,6 +38,7 @@ class MaterialsExport Library: public Base::BaseClass public: Library() = default; + Library(const Library &other) = default; Library(const QString& libraryName, const QString& icon, bool readOnly = true); Library(const QString& libraryName, const QString& icon, diff --git a/src/Mod/Material/App/MaterialManager.cpp b/src/Mod/Material/App/MaterialManager.cpp index eabe92a15b..8b37ff8d9b 100644 --- a/src/Mod/Material/App/MaterialManager.cpp +++ b/src/Mod/Material/App/MaterialManager.cpp @@ -202,7 +202,7 @@ std::shared_ptr MaterialManager::getLibrary(const QString& name return _localManager->getLibrary(name); } -void MaterialManager::createLibrary(const QString& libraryName, const QString& icon, bool readOnly) +void MaterialManager::createLibrary(const QString& /*libraryName*/, const QString& /*icon*/, bool /*readOnly*/) { throw CreationError("Local library requires a path"); } @@ -244,7 +244,7 @@ MaterialManager::libraryMaterials(const QString& libraryName, return _localManager->libraryMaterials(libraryName, filter, options); } -bool MaterialManager::isLocalLibrary(const QString& libraryName) +bool MaterialManager::isLocalLibrary(const QString& /*libraryName*/) { return true; } diff --git a/src/Mod/Material/App/ModelLibrary.cpp b/src/Mod/Material/App/ModelLibrary.cpp index acc118a653..c05e39a6bb 100644 --- a/src/Mod/Material/App/ModelLibrary.cpp +++ b/src/Mod/Material/App/ModelLibrary.cpp @@ -22,6 +22,7 @@ #include "PreCompiled.h" #ifndef _PreComp_ #include +#include #endif #include @@ -38,21 +39,105 @@ using namespace Materials; TYPESYSTEM_SOURCE(Materials::ModelLibrary, Materials::Library) +ModelLibrary::ModelLibrary(const Library& other) + : Library(other) + , _local(false) +{} + ModelLibrary::ModelLibrary(const QString& libraryName, const QString& dir, const QString& icon, bool readOnly) : Library(libraryName, dir, icon, readOnly) -{ - _modelPathMap = std::make_unique>>(); -} + , _local(false) +{} ModelLibrary::ModelLibrary() + : _local(false) { +} + +bool ModelLibrary::isLocal() const +{ + return _local; +} + +void ModelLibrary::setLocal(bool local) +{ + _local = local; +} + +std::shared_ptr>> +ModelLibrary::getModelTree(ModelFilter filter) const +{ + std::shared_ptr>> modelTree = + std::make_shared>>(); + + auto models = ModelManager::getManager().libraryModels(getName()); + for (auto& it : *models) { + auto uuid = std::get<0>(it); + auto path = std::get<1>(it); + auto filename = std::get<2>(it); + + auto model = ModelManager::getManager().getModel(getName(), uuid); + if (ModelManager::passFilter(filter, model->getType())) { + QStringList list = path.split(QLatin1Char('/')); + + // Start at the root + std::shared_ptr>> node = modelTree; + for (auto& itp : list) { + // Add the folder only if it's not already there + if (node->count(itp) == 0) { + auto mapPtr = + std::make_shared>>(); + std::shared_ptr child = std::make_shared(); + child->setFolder(mapPtr); + (*node)[itp] = child; + node = mapPtr; + } + else { + node = (*node)[itp]->getFolder(); + } + } + std::shared_ptr child = std::make_shared(); + child->setUUID(uuid); + child->setData(model); + (*node)[filename] = child; + } + } + + return modelTree; +} + +TYPESYSTEM_SOURCE(Materials::ModelLibraryLocal, Materials::ModelLibrary) + +ModelLibraryLocal::ModelLibraryLocal(const Library& other) + : ModelLibrary(other) +{ + setLocal(true); + _modelPathMap = std::make_unique>>(); } -std::shared_ptr ModelLibrary::getModelByPath(const QString& path) const +ModelLibraryLocal::ModelLibraryLocal(const QString& libraryName, + const QString& dir, + const QString& icon, + bool readOnly) + : ModelLibrary(libraryName, dir, icon, readOnly) +{ + setLocal(true); + + _modelPathMap = std::make_unique>>(); +} + +ModelLibraryLocal::ModelLibraryLocal() +{ + setLocal(true); + + _modelPathMap = std::make_unique>>(); +} + +std::shared_ptr ModelLibraryLocal::getModelByPath(const QString& path) const { QString filePath = getRelativePath(path); try { @@ -64,7 +149,7 @@ std::shared_ptr ModelLibrary::getModelByPath(const QString& path) const } } -std::shared_ptr ModelLibrary::addModel(const Model& model, const QString& path) +std::shared_ptr ModelLibraryLocal::addModel(const Model& model, const QString& path) { QString filePath = getRelativePath(path); QFileInfo info(filePath); @@ -77,46 +162,3 @@ std::shared_ptr ModelLibrary::addModel(const Model& model, const QString& return newModel; } - -std::shared_ptr>> -ModelLibrary::getModelTree(ModelFilter filter) const -{ - std::shared_ptr>> modelTree = - std::make_shared>>(); - - for (auto& it : *_modelPathMap) { - auto filename = it.first; - auto model = it.second; - - if (ModelManager::passFilter(filter, model->getType())) { - QStringList list = filename.split(QStringLiteral("/")); - - // Start at the root - std::shared_ptr>> node = modelTree; - for (auto& itp : list) { - if (ModelManager::isModel(itp)) { - std::shared_ptr child = std::make_shared(); - child->setUUID(model->getUUID()); - child->setData(model); - (*node)[itp] = child; - } - else { - // Add the folder only if it's not already there - if (node->count(itp) == 0) { - auto mapPtr = - std::make_shared>>(); - std::shared_ptr child = std::make_shared(); - child->setFolder(mapPtr); - (*node)[itp] = child; - node = mapPtr; - } - else { - node = (*node)[itp]->getFolder(); - } - } - } - } - } - - return modelTree; -} diff --git a/src/Mod/Material/App/ModelLibrary.h b/src/Mod/Material/App/ModelLibrary.h index ae31c982f0..4264623084 100644 --- a/src/Mod/Material/App/ModelLibrary.h +++ b/src/Mod/Material/App/ModelLibrary.h @@ -35,6 +35,7 @@ #include "Library.h" #include "MaterialValue.h" #include "Model.h" + namespace Materials { @@ -45,12 +46,44 @@ class MaterialsExport ModelLibrary: public Library, public: ModelLibrary(); + ModelLibrary(const Library& other); ModelLibrary(const QString& libraryName, const QString& dir, const QString& icon, bool readOnly = true); + ModelLibrary(const ModelLibrary& other) = delete; ~ModelLibrary() override = default; + bool isLocal() const; + void setLocal(bool local); + + std::shared_ptr>> + getModelTree(ModelFilter filter) const; + + // Use this to get a shared_ptr for *this + std::shared_ptr getptr() + { + return shared_from_this(); + } + +private: + bool _local; +}; + +class MaterialsExport ModelLibraryLocal: public ModelLibrary +{ + TYPESYSTEM_HEADER_WITH_OVERRIDE(); + +public: + ModelLibraryLocal(); + ModelLibraryLocal(const Library& other); + ModelLibraryLocal(const QString& libraryName, + const QString& dir, + const QString& icon, + bool readOnly = true); + ModelLibraryLocal(const ModelLibraryLocal& other) = delete; + ~ModelLibraryLocal() override = default; + bool operator==(const ModelLibrary& library) const { return Library::operator==(library); @@ -63,16 +96,7 @@ public: std::shared_ptr addModel(const Model& model, const QString& path); - // Use this to get a shared_ptr for *this - std::shared_ptr getptr() - { - return shared_from_this(); - } - std::shared_ptr>> - getModelTree(ModelFilter filter) const; - private: - ModelLibrary(const ModelLibrary&); std::unique_ptr>> _modelPathMap; }; @@ -80,5 +104,6 @@ private: } // namespace Materials Q_DECLARE_METATYPE(std::shared_ptr) +Q_DECLARE_METATYPE(std::shared_ptr) #endif // MATERIAL_MODELLIBRARY_H diff --git a/src/Mod/Material/App/ModelLoader.cpp b/src/Mod/Material/App/ModelLoader.cpp index 50e564330e..36dd8b1db6 100644 --- a/src/Mod/Material/App/ModelLoader.cpp +++ b/src/Mod/Material/App/ModelLoader.cpp @@ -21,9 +21,9 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#include #include #include +#include #endif #include @@ -39,7 +39,7 @@ using namespace Materials; -ModelEntry::ModelEntry(const std::shared_ptr& library, +ModelEntry::ModelEntry(const std::shared_ptr& library, const QString& baseName, const QString& modelName, const QString& dir, @@ -58,14 +58,14 @@ std::unique_ptr>> ModelLoader::_mo nullptr; ModelLoader::ModelLoader(std::shared_ptr>> modelMap, - std::shared_ptr>> libraryList) + std::shared_ptr>> libraryList) : _modelMap(modelMap) , _libraryList(libraryList) { loadLibraries(); } -void ModelLoader::addLibrary(std::shared_ptr model) +void ModelLoader::addLibrary(std::shared_ptr model) { _libraryList->push_back(model); } @@ -121,7 +121,8 @@ std::shared_ptr ModelLoader::getModelFromPath(std::shared_ptr model = std::make_shared(library, + auto localLibrary = std::static_pointer_cast(library); + std::shared_ptr model = std::make_shared(localLibrary, QString::fromStdString(base), QString::fromStdString(name), path, @@ -228,6 +229,9 @@ void ModelLoader::addToTree(std::shared_ptr model, 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(); @@ -274,8 +278,7 @@ void ModelLoader::addToTree(std::shared_ptr model, propURL, propDescription); - if (propType == QStringLiteral("2DArray") - || propType == QStringLiteral("3DArray")) { + if (propType == QStringLiteral("2DArray") || propType == QStringLiteral("3DArray")) { // Base::Console().Log("Reading columns\n"); // Read the columns auto cols = yamlProp["Columns"]; @@ -312,7 +315,7 @@ void ModelLoader::addToTree(std::shared_ptr model, (*_modelMap)[uuid] = library->addModel(finalModel, directory); } -void ModelLoader::loadLibrary(std::shared_ptr library) +void ModelLoader::loadLibrary(std::shared_ptr library) { if (_modelEntryMap == nullptr) { _modelEntryMap = std::make_unique>>(); @@ -368,10 +371,9 @@ void ModelLoader::getModelLibraries() 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")); + auto libData = std::make_shared(QStringLiteral("System"), + resourceDir, + QStringLiteral(":/icons/freecad.svg")); _libraryList->push_back(libData); } @@ -387,7 +389,7 @@ void ModelLoader::getModelLibraries() if (modelDir.length() > 0) { QDir dir(modelDir); if (dir.exists()) { - auto libData = std::make_shared(moduleName, modelDir, modelIcon); + auto libData = std::make_shared(moduleName, modelDir, modelIcon); _libraryList->push_back(libData); } } @@ -400,7 +402,7 @@ void ModelLoader::getModelLibraries() if (!resourceDir.isEmpty()) { QDir materialDir(resourceDir); if (materialDir.exists()) { - auto libData = std::make_shared( + auto libData = std::make_shared( QStringLiteral("User"), resourceDir, QStringLiteral(":/icons/preferences-general.svg")); @@ -414,10 +416,9 @@ void ModelLoader::getModelLibraries() if (!resourceDir.isEmpty()) { QDir materialDir(resourceDir); if (materialDir.exists()) { - auto libData = - std::make_shared(QStringLiteral("Custom"), - resourceDir, - QStringLiteral(":/icons/user.svg")); + auto libData = std::make_shared(QStringLiteral("Custom"), + resourceDir, + QStringLiteral(":/icons/user.svg")); _libraryList->push_back(libData); } } diff --git a/src/Mod/Material/App/ModelLoader.h b/src/Mod/Material/App/ModelLoader.h index 3497510993..1f6c21c539 100644 --- a/src/Mod/Material/App/ModelLoader.h +++ b/src/Mod/Material/App/ModelLoader.h @@ -29,6 +29,7 @@ #include #include "Model.h" +#include "ModelLibrary.h" namespace Materials { @@ -36,7 +37,7 @@ namespace Materials class ModelEntry { public: - ModelEntry(const std::shared_ptr& library, + ModelEntry(const std::shared_ptr& library, const QString& baseName, const QString& modelName, const QString& dir, @@ -44,7 +45,7 @@ public: const YAML::Node& modelData); virtual ~ModelEntry() = default; - std::shared_ptr getLibrary() const + std::shared_ptr getLibrary() const { return _library; } @@ -85,7 +86,7 @@ public: private: ModelEntry(); - std::shared_ptr _library; + std::shared_ptr _library; QString _base; QString _name; QString _directory; @@ -98,7 +99,7 @@ class ModelLoader { public: ModelLoader(std::shared_ptr>> modelMap, - std::shared_ptr>> libraryList); + std::shared_ptr>> libraryList); virtual ~ModelLoader() = default; static const QString getUUIDFromPath(const QString& path); @@ -120,13 +121,13 @@ private: std::map, QString>* inheritances); std::shared_ptr getModelFromPath(std::shared_ptr library, const QString& path) const; - void addLibrary(std::shared_ptr model); - void loadLibrary(std::shared_ptr library); + void addLibrary(std::shared_ptr model); + void loadLibrary(std::shared_ptr library); void loadLibraries(); static std::unique_ptr>> _modelEntryMap; std::shared_ptr>> _modelMap; - std::shared_ptr>> _libraryList; + std::shared_ptr>> _libraryList; }; } // namespace Materials diff --git a/src/Mod/Material/App/ModelManager.cpp b/src/Mod/Material/App/ModelManager.cpp index 40b077836c..f0401dfff7 100644 --- a/src/Mod/Material/App/ModelManager.cpp +++ b/src/Mod/Material/App/ModelManager.cpp @@ -34,20 +34,34 @@ #include "ModelManager.h" #include "ModelManagerLocal.h" +#if defined(BUILD_MATERIAL_EXTERNAL) +#include "ModelManagerExternal.h" +#endif using namespace Materials; TYPESYSTEM_SOURCE(Materials::ModelManager, Base::BaseClass) QMutex ModelManager::_mutex; +bool ModelManager::_useExternal = false; ModelManager* ModelManager::_manager = nullptr; std::unique_ptr ModelManager::_localManager; +#if defined(BUILD_MATERIAL_EXTERNAL) +std::unique_ptr ModelManager::_externalManager; +#endif ModelManager::ModelManager() -{} +{ + _hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Material/ExternalInterface"); + _useExternal = _hGrp->GetBool("UseExternal", false); + _hGrp->Attach(this); +} ModelManager::~ModelManager() -{} +{ + _hGrp->Detach(this); +} ModelManager& ModelManager::getManager() { @@ -69,6 +83,22 @@ void ModelManager::initManagers() if (!_localManager) { _localManager = std::make_unique(); } + +#if defined(BUILD_MATERIAL_EXTERNAL) + if (!_externalManager) { + _externalManager = std::make_unique(); + } +#endif +} + +void ModelManager::OnChange(ParameterGrp::SubjectType& rCaller, ParameterGrp::MessageType Reason) +{ + const ParameterGrp& rGrp = static_cast(rCaller); + if (strcmp(Reason, "UseExternal") == 0) { + Base::Console().Log("Use external changed\n"); + _useExternal = rGrp.GetBool("UseExternal", false); + // _dbManager->refresh(); + } } bool ModelManager::isModel(const QString& file) @@ -79,6 +109,11 @@ bool ModelManager::isModel(const QString& file) void ModelManager::cleanup() { return ModelManagerLocal::cleanup(); +#if defined(BUILD_MATERIAL_EXTERNAL) + if (_externalManager) { + _externalManager->cleanup(); + } +#endif } void ModelManager::refresh() @@ -86,9 +121,36 @@ void ModelManager::refresh() _localManager->refresh(); } +//===== +// +// Library management +// +//===== + std::shared_ptr>> ModelManager::getLibraries() { - return _localManager->getLibraries(); + // External libraries take precedence over local libraries + auto libMap = std::map>(); +#if defined(BUILD_MATERIAL_EXTERNAL) + if (_useExternal) { + auto remoteLibraries = _externalManager->getLibraries(); + for (auto& remote : *remoteLibraries) { + libMap.try_emplace(remote->getName(), remote); + } + } +#endif + auto localLibraries = _localManager->getLibraries(); + for (auto& local : *localLibraries) { + libMap.try_emplace(local->getName(), local); + } + + // Consolidate into a single list + auto libraries = std::make_shared>>(); + for (auto libEntry : libMap) { + libraries->push_back(libEntry.second); + } + + return libraries; } std::shared_ptr>> ModelManager::getLocalLibraries() @@ -97,7 +159,24 @@ std::shared_ptr>> ModelManager::getLocal } void ModelManager::createLibrary(const QString& libraryName, const QString& icon, bool readOnly) -{} +{ +#if defined(BUILD_MATERIAL_EXTERNAL) + _externalManager->createLibrary(libraryName, icon, readOnly); +#endif +} + +std::shared_ptr ModelManager::getLibrary(const QString& name) const +{ +#if defined(BUILD_MATERIAL_EXTERNAL) + if (_useExternal) { + auto library = _externalManager->getLibrary(name); + if (library) { + return library; + } + } +#endif + return _localManager->getLibrary(name); +} void ModelManager::createLocalLibrary(const QString& libraryName, const QString& directory, @@ -125,17 +204,64 @@ void ModelManager::removeLibrary(const QString& libraryName) std::shared_ptr>> ModelManager::libraryModels(const QString& libraryName) { +#if defined(BUILD_MATERIAL_EXTERNAL) + if (_useExternal) { + try { + auto models = _externalManager->libraryModels(libraryName); + if (models) { + return models; + } + } + catch (const LibraryNotFound& e) { + } + catch (const InvalidModel& e) { + } + } +#endif return _localManager->libraryModels(libraryName); } bool ModelManager::isLocalLibrary(const QString& libraryName) { +#if defined(BUILD_MATERIAL_EXTERNAL) + if (_useExternal) { + try { + auto lib = _externalManager->getLibrary(libraryName); + if (lib) { + return false; + } + } + catch (const LibraryNotFound& e) { + } + } +#endif return true; } +//===== +// +// Model management +// +//===== + std::shared_ptr>> ModelManager::getModels() { - return _localManager->getModels(); + // External libraries take precedence over local libraries + auto modelMap = std::make_shared>>(); +#if defined(BUILD_MATERIAL_EXTERNAL) + if (_useExternal) { + auto remoteModels = _externalManager->getModels(); + for (auto& remote : *remoteModels) { + modelMap->try_emplace(remote.first, remote.second); + } + } +#endif + auto localModels = _localManager->getModels(); + for (auto& local : *localModels) { + modelMap->try_emplace(local.first, local.second); + } + + return modelMap; } std::shared_ptr>> ModelManager::getLocalModels() @@ -143,8 +269,23 @@ std::shared_ptr>> ModelManager::getLoca return _localManager->getModels(); } +std::shared_ptr ModelManager::getModel(const QString& /*libraryName*/, const QString& uuid) const +{ + // TODO: Search a specific library + return getModel(uuid); +} + std::shared_ptr ModelManager::getModel(const QString& uuid) const { +#if defined(BUILD_MATERIAL_EXTERNAL) + if (_useExternal) { + auto model = _externalManager->getModel(uuid); + if (model) { + return model; + } + } +#endif + // We really want to return the local model if not found, such as for User folder models return _localManager->getModel(uuid); } @@ -158,11 +299,6 @@ std::shared_ptr ModelManager::getModelByPath(const QString& path, const Q return _localManager->getModelByPath(path, lib); } -std::shared_ptr ModelManager::getLibrary(const QString& name) const -{ - return _localManager->getLibrary(name); -} - bool ModelManager::passFilter(ModelFilter filter, Model::ModelType modelType) { switch (filter) { @@ -178,3 +314,51 @@ bool ModelManager::passFilter(ModelFilter filter, Model::ModelType modelType) return false; } + +#if defined(BUILD_MATERIAL_EXTERNAL) +void ModelManager::migrateToExternal(const std::shared_ptr& library) +{ + _externalManager->createLibrary(library->getName(), + library->getIconPath(), + library->isReadOnly()); + + auto models = _localManager->libraryModels(library->getName()); + for (auto& tuple : *models) { + auto uuid = std::get<0>(tuple); + auto path = std::get<1>(tuple); + auto name = std::get<2>(tuple); + Base::Console().Log("\t('%s', '%s', '%s')\n", + uuid.toStdString().c_str(), + path.toStdString().c_str(), + name.toStdString().c_str()); + + auto model = _localManager->getModel(uuid); + _externalManager->migrateModel(library->getName(), path, model); + } +} + +void ModelManager::validateMigration(const std::shared_ptr& library) +{ + auto models = _localManager->libraryModels(library->getName()); + for (auto& tuple : *models) { + auto uuid = std::get<0>(tuple); + auto path = std::get<1>(tuple); + auto name = std::get<2>(tuple); + Base::Console().Log("\t('%s', '%s', '%s')\n", + uuid.toStdString().c_str(), + path.toStdString().c_str(), + name.toStdString().c_str()); + + auto model = _localManager->getModel(uuid); + auto externalModel = _externalManager->getModel(uuid); + model->validate(externalModel); + } +} + +// Cache stats +double ModelManager::modelHitRate() +{ + initManagers(); + return _externalManager->modelHitRate(); +} +#endif diff --git a/src/Mod/Material/App/ModelManager.h b/src/Mod/Material/App/ModelManager.h index 958f0f5fb9..d5853cbaba 100644 --- a/src/Mod/Material/App/ModelManager.h +++ b/src/Mod/Material/App/ModelManager.h @@ -39,7 +39,7 @@ namespace Materials class ModelManagerLocal; class ModelManagerExternal; -class MaterialsExport ModelManager: public Base::BaseClass +class MaterialsExport ModelManager: public Base::BaseClass, ParameterGrp::ObserverType { TYPESYSTEM_HEADER_WITH_OVERRIDE(); @@ -51,8 +51,10 @@ public: static void cleanup(); void refresh(); + // Library management std::shared_ptr>> getLibraries(); std::shared_ptr>> getLocalLibraries(); + std::shared_ptr getLibrary(const QString& name) const; void createLibrary(const QString& libraryName, const QString& icon, bool readOnly = true); void createLocalLibrary(const QString& libraryName, const QString& directory, @@ -65,28 +67,50 @@ public: libraryModels(const QString& libraryName); bool isLocalLibrary(const QString& libraryName); - std::shared_ptr>> getModels(); - std::shared_ptr>> getLocalModels(); + // Folder management + + // Tree management std::shared_ptr>> getModelTree(std::shared_ptr library, ModelFilter filter = ModelFilter_None) const { return library->getModelTree(filter); } + + // Model management + std::shared_ptr>> getModels(); + std::shared_ptr>> getLocalModels(); std::shared_ptr getModel(const QString& uuid) const; + std::shared_ptr getModel(const QString& libraryName, const QString& uuid) const; std::shared_ptr getModelByPath(const QString& path) const; std::shared_ptr getModelByPath(const QString& path, const QString& lib) const; - std::shared_ptr getLibrary(const QString& name) const; static bool isModel(const QString& file); static bool passFilter(ModelFilter filter, Model::ModelType modelType); + /// Observer message from the ParameterGrp + void OnChange(ParameterGrp::SubjectType& rCaller, ParameterGrp::MessageType Reason) override; + +#if defined(BUILD_MATERIAL_EXTERNAL) + void migrateToExternal(const std::shared_ptr& library); + void validateMigration(const std::shared_ptr& library); + + // Cache functions + static double modelHitRate(); +#endif + private: ModelManager(); static void initManagers(); static ModelManager* _manager; static std::unique_ptr _localManager; +#if defined(BUILD_MATERIAL_EXTERNAL) + static std::unique_ptr _externalManager; +#endif static QMutex _mutex; + static bool _useExternal; + + ParameterGrp::handle _hGrp; }; } // namespace Materials diff --git a/src/Mod/Material/App/ModelManagerExternal.cpp b/src/Mod/Material/App/ModelManagerExternal.cpp new file mode 100644 index 0000000000..44b68adfcd --- /dev/null +++ b/src/Mod/Material/App/ModelManagerExternal.cpp @@ -0,0 +1,187 @@ +/*************************************************************************** + * Copyright (c) 2024 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_ +#endif + +#include + +#include + +#include "Model.h" +#include "ModelLoader.h" +#include "ModelManagerExternal.h" +#include "ExternalManager.h" + +using namespace Materials; + +QMutex ModelManagerExternal::_mutex; +LRU::Cache> ModelManagerExternal::_cache(DEFAULT_CACHE_SIZE); + +TYPESYSTEM_SOURCE(Materials::ModelManagerExternal, Base::BaseClass) + +ModelManagerExternal::ModelManagerExternal() +{ + initCache(); +} + +void ModelManagerExternal::initCache() +{ + QMutexLocker locker(&_mutex); + + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Material/ExternalInterface"); + auto cacheSize = hGrp->GetInt("ModelCacheSize", DEFAULT_CACHE_SIZE); + _cache.capacity(cacheSize); + + _cache.monitor(); +} + +void ModelManagerExternal::cleanup() +{ +} + +void ModelManagerExternal::refresh() +{ + resetCache(); +} + +//===== +// +// Library management +// +//===== + +std::shared_ptr>> ModelManagerExternal::getLibraries() +{ + auto libraryList = std::make_shared>>(); + try { + auto externalLibraries = ExternalManager::getManager()->libraries(); + for (auto& entry : *externalLibraries) { + auto library = std::make_shared(*entry); + libraryList->push_back(library); + } + } + catch (const LibraryNotFound& e) { + } + catch (const ConnectionError& e) { + } + + return libraryList; +} + +std::shared_ptr ModelManagerExternal::getLibrary(const QString& name) const +{ + try { + auto lib = ExternalManager::getManager()->getLibrary(name); + auto library = std::make_shared(*lib); + return library; + } + catch (const LibraryNotFound& e) { + throw LibraryNotFound(e); + } + catch (const ConnectionError& e) { + throw LibraryNotFound(e.what()); + } +} + +void ModelManagerExternal::createLibrary(const QString& libraryName, + const QString& icon, + bool readOnly) +{ + ExternalManager::getManager()->createLibrary(libraryName, icon, readOnly); +} + +std::shared_ptr>> +ModelManagerExternal::libraryModels(const QString& libraryName) +{ + return ExternalManager::getManager()->libraryModels(libraryName); +} + +//===== +// +// Model management +// +//===== + +std::shared_ptr ModelManagerExternal::getModel(const QString& uuid) +{ + if (_cache.contains(uuid.toStdString())) { + return _cache.lookup(uuid.toStdString()); + } + try + { + auto model = ExternalManager::getManager()->getModel(uuid); + _cache.emplace(uuid.toStdString(), model); + return model; + } + catch (const ModelNotFound& e) { + _cache.emplace(uuid.toStdString(), nullptr); + return nullptr; + } + catch (const ConnectionError& e) { + _cache.emplace(uuid.toStdString(), nullptr); + return nullptr; + } +} + +std::shared_ptr>> ModelManagerExternal::getModels() +{ + // TODO: Implement an external call + return std::make_shared>>(); +} + +void ModelManagerExternal::addModel(const QString& libraryName, + const QString& path, + const std::shared_ptr& model) +{ + _cache.erase(model->getUUID().toStdString()); + ExternalManager::getManager()->addModel(libraryName, path, model); +} + +void ModelManagerExternal::migrateModel(const QString& libraryName, + const QString& path, + const std::shared_ptr& model) +{ + _cache.erase(model->getUUID().toStdString()); + ExternalManager::getManager()->migrateModel(libraryName, path, model); +} + +//===== +// +// Cache management +// +//===== + +void ModelManagerExternal::resetCache() +{ + _cache.clear(); +} + +double ModelManagerExternal::modelHitRate() +{ + auto hitRate = _cache.stats().hit_rate(); + if (std::isnan(hitRate)) { + return 0; + } + return hitRate; +} diff --git a/src/Mod/Material/App/ModelManagerExternal.h b/src/Mod/Material/App/ModelManagerExternal.h new file mode 100644 index 0000000000..e99a7d9c4d --- /dev/null +++ b/src/Mod/Material/App/ModelManagerExternal.h @@ -0,0 +1,86 @@ +/*************************************************************************** + * Copyright (c) 2024 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 * + * . * + * * + **************************************************************************/ + +#ifndef MATERIAL_MODELMANAGEREXTERNAL_H +#define MATERIAL_MODELMANAGEREXTERNAL_H + +#include +#include + +#include + +#include + +#include "Exceptions.h" +#include "FolderTree.h" +#include "Model.h" +#include "ModelLibrary.h" + +namespace Materials +{ + +class MaterialsExport ModelManagerExternal: public Base::BaseClass +{ + TYPESYSTEM_HEADER_WITH_OVERRIDE(); + +public: + ModelManagerExternal(); + ~ModelManagerExternal() override = default; + + static void cleanup(); + void refresh(); + + static const int DEFAULT_CACHE_SIZE = 100; + + // Library management + std::shared_ptr>> getLibraries(); + std::shared_ptr getLibrary(const QString& name) const; + void createLibrary(const QString& libraryName, + const QString& icon, + bool readOnly = true); + std::shared_ptr>> + libraryModels(const QString& libraryName); + + // Model management + std::shared_ptr getModel(const QString& uuid); + std::shared_ptr>> getModels(); + void + addModel(const QString& libraryName, const QString& path, const std::shared_ptr& model); + void + migrateModel(const QString& libraryName, const QString& path, const std::shared_ptr& model); + + // Cache functions + void resetCache(); + double modelHitRate(); + +private: + static void initCache(); + + static QMutex _mutex; + + // Older platforms (Ubuntu 20.04) can't use QString as the index + // due to a lack of a move constructor + static LRU::Cache> _cache; +}; + +} // namespace Materials + +#endif // MATERIAL_MODELMANAGEREXTERNAL_H \ No newline at end of file diff --git a/src/Mod/Material/App/ModelManagerLocal.cpp b/src/Mod/Material/App/ModelManagerLocal.cpp index 798736c467..cbb4178d1b 100644 --- a/src/Mod/Material/App/ModelManagerLocal.cpp +++ b/src/Mod/Material/App/ModelManagerLocal.cpp @@ -34,7 +34,7 @@ using namespace Materials; -std::shared_ptr>> ModelManagerLocal::_libraryList = nullptr; +std::shared_ptr>> ModelManagerLocal::_libraryList = nullptr; std::shared_ptr>> ModelManagerLocal::_modelMap = nullptr; QMutex ModelManagerLocal::_mutex; @@ -53,7 +53,7 @@ void ModelManagerLocal::initLibraries() if (_modelMap == nullptr) { _modelMap = std::make_shared>>(); if (_libraryList == nullptr) { - _libraryList = std::make_shared>>(); + _libraryList = std::make_shared>>(); } // Load the libraries @@ -98,7 +98,8 @@ void ModelManagerLocal::refresh() std::shared_ptr>> ModelManagerLocal::getLibraries() { - return _libraryList; + return reinterpret_cast>>&>( + _libraryList); } void ModelManagerLocal::createLibrary(const QString& libraryName, @@ -113,10 +114,8 @@ void ModelManagerLocal::createLibrary(const QString& libraryName, } } - auto modelLibrary = std::make_shared(libraryName, directory, icon, readOnly); + auto modelLibrary = std::make_shared(libraryName, directory, icon, readOnly); _libraryList->push_back(modelLibrary); - - // This needs to be persisted somehow } void ModelManagerLocal::renameLibrary(const QString& libraryName, const QString& newName) @@ -192,19 +191,27 @@ std::shared_ptr ModelManagerLocal::getModelByPath(const QString& path) co QString cleanPath = QDir::cleanPath(path); for (auto& library : *_libraryList) { - if (cleanPath.startsWith(library->getDirectory())) { - return library->getModelByPath(cleanPath); + if (library->isLocal()) { + auto localLibrary = std::static_pointer_cast (library); + if (cleanPath.startsWith(localLibrary->getDirectory())) { + return localLibrary->getModelByPath(cleanPath); + } } } - throw MaterialNotFound(); + throw ModelNotFound(); } std::shared_ptr ModelManagerLocal::getModelByPath(const QString& path, const QString& lib) const { auto library = getLibrary(lib); // May throw LibraryNotFound - return library->getModelByPath(path); // May throw ModelNotFound + if (library->isLocal()) { + auto localLibrary = std::static_pointer_cast(library); + return localLibrary->getModelByPath(path); // May throw ModelNotFound + } + + throw ModelNotFound(); } std::shared_ptr ModelManagerLocal::getLibrary(const QString& name) const diff --git a/src/Mod/Material/App/ModelManagerLocal.h b/src/Mod/Material/App/ModelManagerLocal.h index 535a3428c9..824110ec91 100644 --- a/src/Mod/Material/App/ModelManagerLocal.h +++ b/src/Mod/Material/App/ModelManagerLocal.h @@ -77,7 +77,7 @@ public: private: static void initLibraries(); - static std::shared_ptr>> _libraryList; + static std::shared_ptr>> _libraryList; static std::shared_ptr>> _modelMap; static QMutex _mutex; };