From 827ff3eb99cc482d0a565c6bebe46d92f9609732 Mon Sep 17 00:00:00 2001 From: David Carter <38090157+davesrocketshop@users.noreply.github.com> Date: Mon, 28 Apr 2025 08:29:04 -0700 Subject: [PATCH] Materials: Model Manager External Interface (#20825) * Materials: Model Manager External Interface Implement the ModelManagerExternal class for the external Materials interface. This is part of the ongoing merges of code to support external material interfaces. In this PR the ModelManagerExternal class is implemented, along with changes to supporting classes required for this class. * Apply reviewer feedback --- src/Mod/Material/App/CMakeLists.txt | 8 + src/Mod/Material/App/ExternalManager.cpp | 2 +- src/Mod/Material/App/Library.h | 1 + src/Mod/Material/App/MaterialManager.cpp | 4 +- src/Mod/Material/App/ModelLibrary.cpp | 138 +++++++----- src/Mod/Material/App/ModelLibrary.h | 43 +++- src/Mod/Material/App/ModelLoader.cpp | 37 ++-- src/Mod/Material/App/ModelLoader.h | 15 +- src/Mod/Material/App/ModelManager.cpp | 204 +++++++++++++++++- src/Mod/Material/App/ModelManager.h | 32 ++- src/Mod/Material/App/ModelManagerExternal.cpp | 187 ++++++++++++++++ src/Mod/Material/App/ModelManagerExternal.h | 86 ++++++++ src/Mod/Material/App/ModelManagerLocal.cpp | 27 ++- src/Mod/Material/App/ModelManagerLocal.h | 2 +- 14 files changed, 676 insertions(+), 110 deletions(-) create mode 100644 src/Mod/Material/App/ModelManagerExternal.cpp create mode 100644 src/Mod/Material/App/ModelManagerExternal.h 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; };