diff --git a/src/Mod/Material/App/Exceptions.h b/src/Mod/Material/App/Exceptions.h index 69ce4bce15..be9edb8f5b 100644 --- a/src/Mod/Material/App/Exceptions.h +++ b/src/Mod/Material/App/Exceptions.h @@ -300,6 +300,36 @@ public: ~ReplacementError() noexcept override = default; }; +class UpdateError: public Base::Exception +{ +public: + UpdateError() + : Base::Exception("Unable to update object") + {} + explicit UpdateError(const char* msg) + : Base::Exception(msg) + {} + explicit UpdateError(const QString& msg) + : Base::Exception(msg.toStdString().c_str()) + {} + ~UpdateError() noexcept override = default; +}; + +class MoveError: public Base::Exception +{ +public: + MoveError() + : Base::Exception("Unable to move object") + {} + explicit MoveError(const char* msg) + : Base::Exception(msg) + {} + explicit MoveError(const QString& msg) + : Base::Exception(msg.toStdString().c_str()) + {} + ~MoveError() noexcept override = default; +}; + class ConnectionError: public Base::Exception { public: diff --git a/src/Mod/Material/App/ExternalManager.cpp b/src/Mod/Material/App/ExternalManager.cpp index 0e478454b5..07e0f23f51 100644 --- a/src/Mod/Material/App/ExternalManager.cpp +++ b/src/Mod/Material/App/ExternalManager.cpp @@ -36,8 +36,10 @@ #include "ExternalManager.h" #include "MaterialLibrary.h" #include "MaterialLibraryPy.h" +#include "MaterialManager.h" #include "MaterialPy.h" #include "ModelLibrary.h" +#include "ModelManager.h" #include "ModelPy.h" #include "MaterialFilterPy.h" #include "MaterialFilterOptionsPy.h" @@ -98,10 +100,10 @@ void ExternalManager::getConfiguration() void ExternalManager::instantiate() { _instantiated = false; - Base::Console().Log("Loading external manager...\n"); + Base::Console().log("Loading external manager...\n"); if (_moduleName.empty() || _className.empty()) { - Base::Console().Log("External module not defined\n"); + Base::Console().log("External module not defined\n"); return; } @@ -110,7 +112,7 @@ void ExternalManager::instantiate() Py::Module mod(PyImport_ImportModule(_moduleName.c_str()), true); if (mod.isNull()) { - Base::Console().Log(" failed\n"); + Base::Console().log(" failed\n"); return; } @@ -121,14 +123,14 @@ void ExternalManager::instantiate() } if (_instantiated) { - Base::Console().Log("done\n"); + Base::Console().log("done\n"); } else { - Base::Console().Log("failed\n"); + Base::Console().log("failed\n"); } } catch (Py::Exception& e) { - Base::Console().Log("failed\n"); + Base::Console().log("failed\n"); e.clear(); } } @@ -168,8 +170,7 @@ ExternalManager* ExternalManager::getManager() bool ExternalManager::checkMaterialLibraryType(const Py::Object& entry) { - return entry.hasAttr("name") && entry.hasAttr("icon") && entry.hasAttr("readOnly") - && entry.hasAttr("timestamp"); + return entry.hasAttr("name") && entry.hasAttr("icon") && entry.hasAttr("readOnly"); } std::shared_ptr @@ -180,57 +181,53 @@ ExternalManager::libraryFromObject(const Py::Object& entry) } Py::String pyName(entry.getAttr("name")); - Py::Bytes pyIcon(entry.getAttr("icon")); + Py::Bytes pyIcon; + if (entry.getAttr("icon") != Py::None()) { + pyIcon = Py::Bytes(entry.getAttr("icon")); + } Py::Boolean pyReadOnly(entry.getAttr("readOnly")); - Py::String pyTimestamp(entry.getAttr("timestamp")); QString libraryName; if (!pyName.isNone()) { libraryName = QString::fromStdString(pyName.as_string()); } - QString icon; + QByteArray icon; if (!pyIcon.isNone()) { - icon = QString::fromStdString(pyIcon.as_string()); + icon = QByteArray(pyIcon.as_std_string().data(), pyIcon.size()); } bool readOnly = pyReadOnly.as_bool(); - QString timestamp; - if (!pyTimestamp.isNone()) { - timestamp = QString::fromStdString(pyTimestamp.as_string()); - } - - auto library = std::make_shared(libraryName, icon, readOnly, timestamp); + auto library = std::make_shared(libraryName, icon, readOnly); return library; } -bool ExternalManager::checkMaterialObjectType(const Py::Object& entry) +bool ExternalManager::checkMaterialLibraryObjectType(const Py::Object& entry) { return entry.hasAttr("UUID") && entry.hasAttr("path") && entry.hasAttr("name"); } -std::tuple -ExternalManager::materialObjectTypeFromObject(const Py::Object& entry) +LibraryObject ExternalManager::materialLibraryObjectTypeFromObject(const Py::Object& entry) { - QString uuid; + std::string uuid; auto pyUUID = entry.getAttr("UUID"); if (!pyUUID.isNone()) { - uuid = QString::fromStdString(pyUUID.as_string()); + uuid = pyUUID.as_string(); } - QString path; + std::string path; auto pyPath = entry.getAttr("path"); if (!pyPath.isNone()) { - path = QString::fromStdString(pyPath.as_string()); + path = pyPath.as_string(); } - QString name; + std::string name; auto pyName = entry.getAttr("name"); if (!pyName.isNone()) { - name = QString::fromStdString(pyName.as_string()); + name = pyName.as_string(); } - return std::tuple(uuid, path, name); + return LibraryObject(uuid, path, name); } std::shared_ptr>> @@ -251,12 +248,13 @@ ExternalManager::libraries() } } else { - Base::Console().Log("\tlibraries() not found\n"); + Base::Console().log("\tlibraries() not found\n"); throw ConnectionError(); } } catch (Py::Exception& e) { Base::PyException e1; // extract the Python error text + Base::Console().log("Library error %s", e1.what()); throw LibraryNotFound(e1.what()); } @@ -280,7 +278,7 @@ std::shared_ptr>> ExternalManager::modelLib } } else { - Base::Console().Log("\tmodelLibraries() not found\n"); + Base::Console().log("\tmodelLibraries() not found\n"); throw ConnectionError(); } } @@ -309,7 +307,7 @@ std::shared_ptr>> ExternalManager::material } } else { - Base::Console().Log("\tmaterialLibraries() not found\n"); + Base::Console().log("\tmaterialLibraries() not found\n"); throw ConnectionError(); } } @@ -332,24 +330,26 @@ std::shared_ptr ExternalManager::getLibrary(const QString& name) Py::Callable libraries(_managerObject.getAttr("getLibrary")); Py::Tuple args(1); args.setItem(0, Py::String(name.toStdString())); - Py::Tuple result(libraries.apply(args)); + Py::Object result(libraries.apply(args)); - Py::Object libObject = result.getItem(0); - auto lib = libraryFromObject(Py::Tuple(libObject)); + auto lib = libraryFromObject(result); return std::make_shared(*lib); } else { - Base::Console().Log("\tgetLibrary() not found\n"); + Base::Console().log("\tgetLibrary() not found\n"); throw ConnectionError(); } } + catch (const InvalidLibrary&) { + throw LibraryNotFound(); + } catch (Py::Exception& e) { Base::PyException e1; // extract the Python error text - throw CreationError(e1.what()); + throw LibraryNotFound(e1.what()); } } -void ExternalManager::createLibrary(const QString& libraryName, const QString& icon, bool readOnly) +void ExternalManager::createLibrary(const QString& libraryName, const QByteArray& icon, bool readOnly) { connect(); @@ -359,12 +359,12 @@ void ExternalManager::createLibrary(const QString& libraryName, const QString& i Py::Callable libraries(_managerObject.getAttr("createLibrary")); Py::Tuple args(3); args.setItem(0, Py::String(libraryName.toStdString())); - args.setItem(1, Py::String(icon.toStdString())); + args.setItem(1, Py::Bytes(icon.data(), icon.size())); args.setItem(2, Py::Boolean(readOnly)); libraries.apply(args); // No return expected } else { - Base::Console().Log("\tcreateLibrary() not found\n"); + Base::Console().log("\tcreateLibrary() not found\n"); throw ConnectionError(); } } @@ -388,7 +388,7 @@ void ExternalManager::renameLibrary(const QString& libraryName, const QString& n libraries.apply(args); // No return expected } else { - Base::Console().Log("\trenameLibrary() not found\n"); + Base::Console().log("\trenameLibrary() not found\n"); throw ConnectionError(); } } @@ -398,7 +398,7 @@ void ExternalManager::renameLibrary(const QString& libraryName, const QString& n } } -void ExternalManager::changeIcon(const QString& libraryName, const QString& icon) +void ExternalManager::changeIcon(const QString& libraryName, const QByteArray& icon) { connect(); @@ -408,11 +408,11 @@ void ExternalManager::changeIcon(const QString& libraryName, const QString& icon Py::Callable libraries(_managerObject.getAttr("changeIcon")); Py::Tuple args(2); args.setItem(0, Py::String(libraryName.toStdString())); - args.setItem(1, Py::String(icon.toStdString())); + args.setItem(1, Py::Bytes(icon.data(), icon.size())); libraries.apply(args); // No return expected } else { - Base::Console().Log("\tchangeIcon() not found\n"); + Base::Console().log("\tchangeIcon() not found\n"); throw ConnectionError(); } } @@ -435,7 +435,7 @@ void ExternalManager::removeLibrary(const QString& libraryName) libraries.apply(args); // No return expected } else { - Base::Console().Log("\tremoveLibrary() not found\n"); + Base::Console().log("\tremoveLibrary() not found\n"); throw ConnectionError(); } } @@ -445,10 +445,10 @@ void ExternalManager::removeLibrary(const QString& libraryName) } } -std::shared_ptr>> +std::shared_ptr> ExternalManager::libraryModels(const QString& libraryName) { - auto modelList = std::make_shared>>(); + auto modelList = std::make_shared>(); connect(); @@ -461,15 +461,15 @@ ExternalManager::libraryModels(const QString& libraryName) Py::List list(libraries.apply(args)); for (auto library : list) { auto entry = Py::Object(library); - if (!checkMaterialObjectType(entry)) { + if (!checkMaterialLibraryObjectType(entry)) { throw InvalidModel(); } - modelList->push_back(materialObjectTypeFromObject(entry)); + modelList->push_back(materialLibraryObjectTypeFromObject(entry)); } } else { - Base::Console().Log("\tlibraryModels() not found\n"); + Base::Console().log("\tlibraryModels() not found\n"); throw ConnectionError(); } } @@ -481,10 +481,10 @@ ExternalManager::libraryModels(const QString& libraryName) return modelList; } -std::shared_ptr>> +std::shared_ptr> ExternalManager::libraryMaterials(const QString& libraryName) { - auto materialList = std::make_shared>>(); + auto materialList = std::make_shared>(); connect(); @@ -497,15 +497,15 @@ ExternalManager::libraryMaterials(const QString& libraryName) Py::List list(libraries.apply(args)); for (auto library : list) { auto entry = Py::Object(library); - if (!checkMaterialObjectType(entry)) { + if (!checkMaterialLibraryObjectType(entry)) { throw InvalidMaterial(); } - materialList->push_back(materialObjectTypeFromObject(entry)); + materialList->push_back(materialLibraryObjectTypeFromObject(entry)); } } else { - Base::Console().Log("\tlibraryMaterials() not found\n"); + Base::Console().log("\tlibraryMaterials() not found\n"); throw ConnectionError(); } } @@ -517,12 +517,12 @@ ExternalManager::libraryMaterials(const QString& libraryName) return materialList; } -std::shared_ptr>> +std::shared_ptr> ExternalManager::libraryMaterials(const QString& libraryName, const std::shared_ptr& filter, const MaterialFilterOptions& options) { - auto materialList = std::make_shared>>(); + auto materialList = std::make_shared>(); connect(); @@ -545,15 +545,15 @@ ExternalManager::libraryMaterials(const QString& libraryName, Py::List list(libraries.apply(args)); for (auto library : list) { auto entry = Py::Object(library); - if (!checkMaterialObjectType(entry)) { + if (!checkMaterialLibraryObjectType(entry)) { throw InvalidMaterial(); } - materialList->push_back(materialObjectTypeFromObject(entry)); + materialList->push_back(materialLibraryObjectTypeFromObject(entry)); } } else { - Base::Console().Log("\tlibraryMaterials() not found\n"); + Base::Console().log("\tlibraryMaterials() not found\n"); throw ConnectionError(); } } @@ -565,12 +565,162 @@ ExternalManager::libraryMaterials(const QString& libraryName, return materialList; } +std::shared_ptr> ExternalManager::libraryFolders(const QString& libraryName) +{ + auto folderList = std::make_shared>(); + + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("libraryFolders")) { + Py::Callable folders(_managerObject.getAttr("libraryFolders")); + Py::Tuple args(1); + args.setItem(0, Py::String(libraryName.toStdString())); + Py::List list(folders.apply(args)); + for (auto folder : list) { + auto entry = Py::Object(folder); + Py::String pyName(entry.getAttr("name")); + + QString folderName; + if (!pyName.isNone()) { + folderName = QString::fromStdString(pyName.as_string()); + } + + folderList->push_back(folderName); + } + } + else { + Base::Console().log("\tlibraryFolders() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw LibraryNotFound(e1.what()); + } + + return folderList; +} + +//===== +// +// Folder management +// +//===== + +void ExternalManager::createFolder(const QString& libraryName, const QString& path) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("createFolder")) { + Py::Callable libraries(_managerObject.getAttr("createFolder")); + Py::Tuple args(2); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(path.toStdString())); + Py::Object result(libraries.apply(args)); + } + else { + Base::Console().log("\tcreateFolder() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw CreationError(e1.what()); + } +} + +void ExternalManager::renameFolder(const QString& libraryName, + const QString& oldPath, + const QString& newPath) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("renameFolder")) { + Py::Callable libraries(_managerObject.getAttr("renameFolder")); + Py::Tuple args(3); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(oldPath.toStdString())); + args.setItem(2, Py::String(newPath.toStdString())); + Py::Object result(libraries.apply(args)); + } + else { + Base::Console().log("\trenameFolder() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw RenameError(e1.what()); + } +} + +void ExternalManager::deleteRecursive(const QString& libraryName, const QString& path) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("deleteRecursive")) { + Py::Callable libraries(_managerObject.getAttr("deleteRecursive")); + Py::Tuple args(2); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(path.toStdString())); + Py::Object result(libraries.apply(args)); + } + else { + Base::Console().log("\tdeleteRecursive() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw DeleteError(e1.what()); + } +} + //===== // // Model management // //===== +bool ExternalManager::checkModelObjectType(const Py::Object& entry) +{ + return entry.hasAttr("libraryName") && entry.hasAttr("model"); +} + +std::shared_ptr ExternalManager::modelFromObject(const Py::Object& entry, + const QString& uuid) +{ + if (!checkModelObjectType(entry)) { + throw InvalidModel(); + } + + Py::String pyName(entry.getAttr("libraryName")); + Py::Object modelObject(entry.getAttr("model")); + + QString libraryName; + if (!pyName.isNone()) { + libraryName = QString::fromStdString(pyName.as_string()); + } + + // Using this call will use caching, whereas using our class function will not + auto library = ModelManager::getManager().getLibrary(libraryName); + + Model* model = static_cast(*modelObject)->getModelPtr(); + model->setUUID(uuid); + model->setLibrary(library); + auto shared = std::make_shared(*model); + + return shared; +} + std::shared_ptr ExternalManager::getModel(const QString& uuid) { connect(); @@ -581,36 +731,14 @@ std::shared_ptr ExternalManager::getModel(const QString& uuid) Py::Callable libraries(_managerObject.getAttr("getModel")); Py::Tuple args(1); args.setItem(0, Py::String(uuid.toStdString())); - Py::Tuple result(libraries.apply(args)); // ignore return for now + Py::Object result(libraries.apply(args)); // ignore return for now - Py::Object uuidObject = result.getItem(0); - Py::Tuple libraryObject(result.getItem(1)); - Py::Object modelObject = result.getItem(2); - - Py::Object pyName = libraryObject.getItem(0); - Py::Object pyIcon = libraryObject.getItem(1); - Py::Object readOnly = libraryObject.getItem(2); - - QString name; - if (!pyName.isNone()) { - name = QString::fromStdString(pyName.as_string()); - } - QString icon; - if (!pyIcon.isNone()) { - icon = QString::fromStdString(pyIcon.as_string()); - } - auto library = - std::make_shared(name, QString(), icon, readOnly.as_bool()); - - Model* model = static_cast(*modelObject)->getModelPtr(); - model->setUUID(uuid); - model->setLibrary(library); - auto shared = std::make_shared(*model); + auto shared = modelFromObject(result, uuid); return shared; } else { - Base::Console().Log("\tgetModel() not found\n"); + Base::Console().log("\tgetModel() not found\n"); throw ConnectionError(); } } @@ -637,7 +765,7 @@ void ExternalManager::addModel(const QString& libraryName, libraries.apply(args); // No return expected } else { - Base::Console().Log("\taddModel() not found\n"); + Base::Console().log("\taddModel() not found\n"); throw ConnectionError(); } } @@ -664,7 +792,7 @@ void ExternalManager::migrateModel(const QString& libraryName, libraries.apply(args); // No return expected } else { - Base::Console().Log("\tmigrateModel() not found\n"); + Base::Console().log("\tmigrateModel() not found\n"); throw ConnectionError(); } } @@ -674,12 +802,174 @@ void ExternalManager::migrateModel(const QString& libraryName, } } +void ExternalManager::updateModel(const QString& libraryName, + const QString& path, + const std::shared_ptr& model) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("updateModel")) { + Py::Callable libraries(_managerObject.getAttr("updateModel")); + Py::Tuple args(3); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(path.toStdString())); + args.setItem(2, Py::Object(new ModelPy(new Model(*model)), true)); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\tupdateModel() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw UpdateError(e1.what()); + } +} + +void ExternalManager::setModelPath(const QString& libraryName, + const QString& path, + const QString& uuid) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("setModelPath")) { + Py::Callable libraries(_managerObject.getAttr("setModelPath")); + Py::Tuple args(3); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(path.toStdString())); + args.setItem(2, Py::String(uuid.toStdString())); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\tsetModelPath() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw UpdateError(e1.what()); + } +} + +void ExternalManager::renameModel(const QString& libraryName, + const QString& name, + const QString& uuid) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("renameModel")) { + Py::Callable libraries(_managerObject.getAttr("renameModel")); + Py::Tuple args(3); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(name.toStdString())); + args.setItem(2, Py::String(uuid.toStdString())); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\trenameModel() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw RenameError(e1.what()); + } +} + +void ExternalManager::moveModel(const QString& libraryName, + const QString& path, + const QString& uuid) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("moveModel")) { + Py::Callable libraries(_managerObject.getAttr("moveModel")); + Py::Tuple args(3); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(path.toStdString())); + args.setItem(2, Py::String(uuid.toStdString())); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\tmoveModel() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw MoveError(e1.what()); + } +} + +void ExternalManager::removeModel(const QString& uuid) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("removeModel")) { + Py::Callable libraries(_managerObject.getAttr("removeModel")); + Py::Tuple args(1); + args.setItem(0, Py::String(uuid.toStdString())); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\tremoveModel() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw DeleteError(e1.what()); + } +} + //===== // // Material management // //===== +bool ExternalManager::checkMaterialObjectType(const Py::Object& entry) +{ + return entry.hasAttr("libraryName") && entry.hasAttr("material"); +} + +std::shared_ptr ExternalManager::materialFromObject(const Py::Object& entry, + const QString& uuid) +{ + if (!checkMaterialObjectType(entry)) { + throw InvalidMaterial(); + } + + Py::String pyName(entry.getAttr("libraryName")); + Py::Object materialObject(entry.getAttr("material")); + + QString libraryName; + if (!pyName.isNone()) { + libraryName = QString::fromStdString(pyName.as_string()); + } + + // Using this call will use caching, whereas using our class function will not + auto library = MaterialManager::getManager().getLibrary(libraryName); + + Material* material = static_cast(*materialObject)->getMaterialPtr(); + material->setUUID(uuid); + material->setLibrary(library); + auto shared = std::make_shared(*material); + + return shared; +} + std::shared_ptr ExternalManager::getMaterial(const QString& uuid) { connect(); @@ -690,36 +980,14 @@ std::shared_ptr ExternalManager::getMaterial(const QString& uuid) Py::Callable libraries(_managerObject.getAttr("getMaterial")); Py::Tuple args(1); args.setItem(0, Py::String(uuid.toStdString())); - Py::Tuple result(libraries.apply(args)); + Py::Object result(libraries.apply(args)); - Py::Object uuidObject = result.getItem(0); - Py::Tuple libraryObject(result.getItem(1)); - Py::Object materialObject = result.getItem(2); - - Py::Object pyName = libraryObject.getItem(0); - Py::Object pyIcon = libraryObject.getItem(1); - Py::Object readOnly = libraryObject.getItem(2); - - QString name; - if (!pyName.isNone()) { - name = QString::fromStdString(pyName.as_string()); - } - QString icon; - if (!pyIcon.isNone()) { - icon = QString::fromStdString(pyIcon.as_string()); - } - auto library = - std::make_shared(name, QString(), icon, readOnly.as_bool()); - - Material* material = static_cast(*materialObject)->getMaterialPtr(); - material->setUUID(uuid); - material->setLibrary(library); - auto shared = std::make_shared(*material); + auto shared = materialFromObject(result, uuid); return shared; } else { - Base::Console().Log("\tgetMaterial() not found\n"); + Base::Console().log("\tgetMaterial() not found\n"); throw ConnectionError(); } } @@ -746,7 +1014,7 @@ void ExternalManager::addMaterial(const QString& libraryName, libraries.apply(args); // No return expected } else { - Base::Console().Log("\taddMaterial() not found\n"); + Base::Console().log("\taddMaterial() not found\n"); throw ConnectionError(); } } @@ -774,7 +1042,7 @@ void ExternalManager::migrateMaterial(const QString& libraryName, libraries.apply(args); // No return expected } else { - Base::Console().Log("\tmigrateMaterial() not found\n"); + Base::Console().log("\tmigrateMaterial() not found\n"); throw ConnectionError(); } } @@ -783,3 +1051,134 @@ void ExternalManager::migrateMaterial(const QString& libraryName, throw CreationError(e1.what()); } } + +void ExternalManager::updateMaterial(const QString& libraryName, + const QString& path, + const std::shared_ptr& material) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("updateMaterial")) { + Py::Callable libraries(_managerObject.getAttr("updateMaterial")); + Py::Tuple args(3); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(path.toStdString())); + args.setItem(2, Py::Object(new MaterialPy(new Material(*material)), true)); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\tupdateMaterial() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw UpdateError(e1.what()); + } +} + +void ExternalManager::setMaterialPath(const QString& libraryName, + const QString& path, + const QString& uuid) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("setMaterialPath")) { + Py::Callable libraries(_managerObject.getAttr("setMaterialPath")); + Py::Tuple args(3); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(path.toStdString())); + args.setItem(2, Py::String(uuid.toStdString())); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\tsetMaterialPath() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw UpdateError(e1.what()); + } +} + +void ExternalManager::renameMaterial(const QString& libraryName, + const QString& name, + const QString& uuid) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("renameMaterial")) { + Py::Callable libraries(_managerObject.getAttr("renameMaterial")); + Py::Tuple args(3); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(name.toStdString())); + args.setItem(2, Py::String(uuid.toStdString())); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\trenameMaterial() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw RenameError(e1.what()); + } +} + +void ExternalManager::moveMaterial(const QString& libraryName, + const QString& path, + const QString& uuid) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("moveMaterial")) { + Py::Callable libraries(_managerObject.getAttr("moveMaterial")); + Py::Tuple args(3); + args.setItem(0, Py::String(libraryName.toStdString())); + args.setItem(1, Py::String(path.toStdString())); + args.setItem(2, Py::String(uuid.toStdString())); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\tmoveMaterial() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw MoveError(e1.what()); + } +} + +void ExternalManager::removeMaterial(const QString& uuid) +{ + connect(); + + Base::PyGILStateLocker lock; + try { + if (_managerObject.hasAttr("removeMaterial")) { + Py::Callable libraries(_managerObject.getAttr("removeMaterial")); + Py::Tuple args(1); + args.setItem(0, Py::String(uuid.toStdString())); + libraries.apply(args); // No return expected + } + else { + Base::Console().log("\tremoveMaterial() not found\n"); + throw ConnectionError(); + } + } + catch (Py::Exception& e) { + Base::PyException e1; // extract the Python error text + throw DeleteError(e1.what()); + } +} diff --git a/src/Mod/Material/App/ExternalManager.h b/src/Mod/Material/App/ExternalManager.h index 91f92b49ca..3f3e1a10cf 100644 --- a/src/Mod/Material/App/ExternalManager.h +++ b/src/Mod/Material/App/ExternalManager.h @@ -29,11 +29,13 @@ class QMutex; class QString; +class QByteArray; namespace Materials { class Library; +class LibraryObject; class Material; class Model; class MaterialFilter; @@ -53,18 +55,26 @@ public: std::shared_ptr>> modelLibraries(); std::shared_ptr>> materialLibraries(); std::shared_ptr getLibrary(const QString& name); - void createLibrary(const QString& libraryName, const QString& icon, bool readOnly = true); + void createLibrary(const QString& libraryName, + const QByteArray& icon, + bool readOnly = true); void renameLibrary(const QString& libraryName, const QString& newName); - void changeIcon(const QString& libraryName, const QString& icon); + void changeIcon(const QString& libraryName, const QByteArray& icon); void removeLibrary(const QString& libraryName); - std::shared_ptr>> - libraryModels(const QString& libraryName); - std::shared_ptr>> - libraryMaterials(const QString& libraryName); - std::shared_ptr>> + std::shared_ptr> libraryModels(const QString& libraryName); + std::shared_ptr> libraryMaterials(const QString& libraryName); + std::shared_ptr> libraryMaterials(const QString& libraryName, const std::shared_ptr& filter, const MaterialFilterOptions& options); + std::shared_ptr> libraryFolders(const QString& libraryName); + + // Folder management + void createFolder(const QString& libraryName, const QString& path); + void renameFolder(const QString& libraryName, + const QString& oldPath, + const QString& newPath); + void deleteRecursive(const QString& libraryName, const QString& path); // Model management std::shared_ptr getModel(const QString& uuid); @@ -72,6 +82,13 @@ public: 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); + void updateModel(const QString& libraryName, + const QString& path, + const std::shared_ptr& model); + void setModelPath(const QString& libraryName, const QString& path, const QString& uuid); + void renameModel(const QString& libraryName, const QString& name, const QString& uuid); + void moveModel(const QString& libraryName, const QString& path, const QString& uuid); + void removeModel(const QString& uuid); // Material management std::shared_ptr getMaterial(const QString& uuid); @@ -81,6 +98,13 @@ public: void migrateMaterial(const QString& libraryName, const QString& path, const std::shared_ptr& material); + void updateMaterial(const QString& libraryName, + const QString& path, + const std::shared_ptr& material); + void setMaterialPath(const QString& libraryName, const QString& path, const QString& uuid); + void renameMaterial(const QString& libraryName, const QString& name, const QString& uuid); + void moveMaterial(const QString& libraryName, const QString& path, const QString& uuid); + void removeMaterial(const QString& uuid); private: ExternalManager(); @@ -92,8 +116,12 @@ private: void connect(); bool checkMaterialLibraryType(const Py::Object& entry); std::shared_ptr libraryFromObject(const Py::Object& entry); + bool checkMaterialLibraryObjectType(const Py::Object& entry); + LibraryObject materialLibraryObjectTypeFromObject(const Py::Object& entry); + bool checkModelObjectType(const Py::Object& entry); + std::shared_ptr modelFromObject(const Py::Object& entry, const QString& uuid); bool checkMaterialObjectType(const Py::Object& entry); - std::tuple materialObjectTypeFromObject(const Py::Object& entry); + std::shared_ptr materialFromObject(const Py::Object& entry, const QString& uuid); static ExternalManager* _manager; static QMutex _mutex; diff --git a/src/Mod/Material/App/FolderTree.h b/src/Mod/Material/App/FolderTree.h index 1482016b9a..60eea6b262 100644 --- a/src/Mod/Material/App/FolderTree.h +++ b/src/Mod/Material/App/FolderTree.h @@ -43,6 +43,8 @@ public: FolderTreeNode() : _type(NodeType::UnknownNode) + , _oldFormat(false) + , _readOnly(false) {} virtual ~FolderTreeNode() = default; @@ -76,6 +78,14 @@ public: assert(_type == NodeType::DataNode); return _uuid; } + bool isOldFormat() const + { + return _oldFormat; + } + bool isReadOnly() const + { + return _readOnly; + } void setFolder(std::shared_ptr>>> folder) { @@ -92,12 +102,22 @@ public: setType(NodeType::DataNode); _uuid = uuid; } + void setOldFormat(bool oldFormat) + { + _oldFormat = oldFormat; + } + void setReadOnly(bool readOnly) + { + _readOnly = readOnly; + } private: NodeType _type; std::shared_ptr>>> _folder; QString _uuid; std::shared_ptr _data; + bool _oldFormat; + bool _readOnly; }; } // namespace Materials diff --git a/src/Mod/Material/App/Library.cpp b/src/Mod/Material/App/Library.cpp index dcc1c0400a..98d3bd54d4 100644 --- a/src/Mod/Material/App/Library.cpp +++ b/src/Mod/Material/App/Library.cpp @@ -34,28 +34,60 @@ using namespace Materials; TYPESYSTEM_SOURCE(Materials::Library, Base::BaseClass) -Library::Library(const QString& libraryName, const QString& icon, bool readOnly) +Library::Library(const QString& libraryName, const QString& iconPath, bool readOnly) : _name(libraryName) - , _iconPath(icon) , _readOnly(readOnly) + , _local(false) +{ + setIcon(iconPath); +} + +Library::Library(const QString& libraryName, const QByteArray& icon, bool readOnly) + : _name(libraryName) + , _icon(icon) + , _readOnly(readOnly) + , _local(false) {} Library::Library(const QString& libraryName, - const QString& icon, - bool readOnly, - const QString& timestamp) - : _name(libraryName) - , _iconPath(icon) - , _readOnly(readOnly) - , _timestamp(timestamp) -{} - -Library::Library(const QString& libraryName, const QString& dir, const QString& icon, bool readOnly) + const QString& dir, + const QString& iconPath, + bool readOnly) : _name(libraryName) , _directory(QDir::cleanPath(dir)) - , _iconPath(icon) , _readOnly(readOnly) -{} + , _local(false) +{ + setIcon(iconPath); +} + +QByteArray Library::getIcon(const QString& iconPath) +{ + QFile file(iconPath); + if (!file.open(QIODevice::ReadOnly)) { + Base::Console().log("Failed to open icon file '%s'\n", iconPath.toStdString().c_str()); + return QByteArray(); // Return an empty QByteArray if file opening fails + } + + QByteArray data = file.readAll(); + file.close(); + return data; +} + +void Library::setIcon(const QString& iconPath) +{ + _icon = getIcon(iconPath); +} + +bool Library::isLocal() const +{ + return _local; +} + +void Library::setLocal(bool local) +{ + _local = local; +} bool Library::operator==(const Library& library) const { @@ -67,10 +99,8 @@ void Library::validate(const Library& remote) const if (getName() != remote.getName()) { throw InvalidLibrary("Library names don't match"); } - if (getIconPath() != remote.getIconPath()) { - Base::Console().log("Icon path 1 '%s'\n", getIconPath().toStdString().c_str()); - Base::Console().log("Icon path 2 '%s'\n", remote.getIconPath().toStdString().c_str()); - throw InvalidLibrary("Library icon paths don't match"); + if (getIcon() != remote.getIcon()) { + throw InvalidLibrary("Library icons don't match"); } // Local and remote paths will differ diff --git a/src/Mod/Material/App/Library.h b/src/Mod/Material/App/Library.h index a46f57f34d..eadb09ed66 100644 --- a/src/Mod/Material/App/Library.h +++ b/src/Mod/Material/App/Library.h @@ -23,6 +23,7 @@ #define MATERIAL_LIBRARY_H #include +#include #include #include @@ -40,16 +41,16 @@ 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, - bool readOnly, - const QString& timestamp); + Library(const QString& libraryName, const QByteArray& icon, bool readOnly); Library(const QString& libraryName, const QString& dir, - const QString& icon, + const QString& iconPath, bool readOnly = true); ~Library() override = default; + bool isLocal() const; + void setLocal(bool local); + QString getName() const { return _name; @@ -63,13 +64,19 @@ public: return (_name == name); } - QString getIconPath() const + QByteArray getIcon() const { - return _iconPath; + return _icon; } - void setIconPath(const QString& icon) + static QByteArray getIcon(const QString& iconPath); + void setIcon(const QByteArray& icon) { - _iconPath = icon; + _icon = icon; + } + void setIcon(const QString& iconPath); + bool hasIcon() const + { + return !_icon.isEmpty(); } bool isReadOnly() const { @@ -92,14 +99,6 @@ public: { return QDir(_directory).absolutePath(); } - QString getTimestamp() const - { - return _timestamp; - } - void setTimestamp(const QString& timestamp) - { - _timestamp = timestamp; - } bool operator==(const Library& library) const; bool operator!=(const Library& library) const @@ -118,9 +117,72 @@ public: private: QString _name; QString _directory; - QString _iconPath; + QByteArray _icon; bool _readOnly; - QString _timestamp; + + bool _local; + + QByteArray loadByteArrayFromFile(const QString& filePath) const; +}; + +class MaterialsExport LibraryObject +{ +public: + LibraryObject(const QString& uuid, const QString& path, const QString& name) + : _uuid(uuid) + , _path(path) + , _name(name) + {} + LibraryObject(const std::string& uuid, const std::string& path, const std::string& name) + : _uuid(QString::fromStdString(uuid)) + , _path(QString::fromStdString(path)) + , _name(QString::fromStdString(name)) + {} + ~LibraryObject() = default; + + void setUUID(const QString& uuid) + { + _uuid = uuid; + } + void setUUID(const std::string& uuid) + { + _uuid = QString::fromStdString(uuid); + } + QString getUUID() const + { + return _uuid; + } + + void setPath(const QString& path) + { + _path = path; + } + void setPath(const std::string& path) + { + _path = QString::fromStdString(path); + } + QString getPath() const + { + return _path; + } + + void setName(const QString& name) + { + _name = name; + } + void setName(const std::string& name) + { + _name = QString::fromStdString(name); + } + QString getName() const + { + return _name; + } + +private: + QString _uuid; + QString _path; + QString _name; }; } // namespace Materials diff --git a/src/Mod/Material/App/Material.pyi b/src/Mod/Material/App/Material.pyi index 46c1b75c0f..7a95608df9 100644 --- a/src/Mod/Material/App/Material.pyi +++ b/src/Mod/Material/App/Material.pyi @@ -19,22 +19,22 @@ class Material(BaseClass): """ LibraryName: Final[str] = ... - """Model library name.""" + """Material library name.""" LibraryRoot: Final[str] = ... - """Model library path.""" + """Material library path.""" - LibraryIcon: Final[str] = ... - """Model icon path.""" + LibraryIcon: Final[bytes] = ... + """Material icon.""" Name: str = ... - """Model name.""" + """Material name.""" Directory: str = ... - """Model directory relative to the library root.""" + """Material directory relative to the library root.""" UUID: Final[str] = ... - """Unique model identifier. This is only valid after the material is saved.""" + """Unique material identifier. This is only valid after the material is saved.""" Description: str = ... """Description of the material.""" diff --git a/src/Mod/Material/App/MaterialAPI/MaterialManagerExternal.py b/src/Mod/Material/App/MaterialAPI/MaterialManagerExternal.py index 695be6fb2e..9900985f65 100644 --- a/src/Mod/Material/App/MaterialAPI/MaterialManagerExternal.py +++ b/src/Mod/Material/App/MaterialAPI/MaterialManagerExternal.py @@ -32,7 +32,6 @@ class MaterialLibraryType: name: str icon: bytes readOnly: bool - timestamp: str @dataclass class MaterialLibraryObjectType: @@ -40,6 +39,16 @@ class MaterialLibraryObjectType: path: str name: str +@dataclass +class ModelObjectType: + libraryName: str + model: Materials.Model + +@dataclass +class MaterialObjectType: + libraryName: str + material: Materials.Material + class MaterialManagerExternal(ABC): """Abstract base class for all external material managers @@ -63,9 +72,7 @@ class MaterialManagerExternal(ABC): The list contains a series of tuples describing all libraries managed by this module. Each tuple contains the library name, icon, a boolean to indicate - if it is a read only library, and a timestamp that indicates when it was last - modified.""" - pass + if it is a read only library.""" @abstractmethod def modelLibraries(self) -> list[MaterialLibraryType]: @@ -73,12 +80,10 @@ class MaterialManagerExternal(ABC): The list contains a series of tuples describing all libraries managed by this module. Each tuple contains the library name, icon, and a boolean to indicate - if it is a read only library, and a timestamp that indicates when it was last - modified. + if it is a read only library. This differs from the libraries() function in that it only returns libraries containing model objects.""" - pass @abstractmethod def materialLibraries(self) -> list[MaterialLibraryType]: @@ -86,164 +91,165 @@ class MaterialManagerExternal(ABC): The list contains a series of tuples describing all libraries managed by this module. Each tuple contains the library name, icon, and a boolean to indicate - if it is a read only library, and a timestamp that indicates when it was last - modified. + if it is a read only library. This differs from the libraries() function in that it only returns libraries containing material objects.""" - pass @abstractmethod - def getLibrary(self, name: str) -> tuple: + def getLibrary(self, libraryName: str) -> MaterialLibraryType: """Get the library Retrieve the library with the given name""" - pass @abstractmethod - def createLibrary(self, name: str, icon: bytes, readOnly: bool) -> None: + def createLibrary(self, libraryName: str, icon: bytes, readOnly: bool) -> None: """Create a new library - Create a new library with the given name""" - pass + Create a new library with the given name.""" @abstractmethod def renameLibrary(self, oldName: str, newName: str) -> None: """Rename an existing library Change the name of an existing library""" - pass @abstractmethod - def changeIcon(self, name: str, icon: bytes) -> None: + def changeIcon(self, libraryName: str, icon: bytes) -> None: """Change the library icon Change the library icon""" - pass @abstractmethod - def removeLibrary(self, library: str) -> None: + def removeLibrary(self, libraryName: str) -> None: """Delete a library and its contents Deletes the library and any models or materials it contains""" - pass @abstractmethod - def libraryModels(self, library: str) -> list[MaterialLibraryObjectType]: + def libraryModels(self, libraryName: str) -> list[MaterialLibraryObjectType]: """Returns a list of models managed by this library Each list entry is a tuple containing the UUID, path, and name of the model""" - pass @abstractmethod - def libraryMaterials(self, library: str, - filter: Materials.MaterialFilter = None, - options: Materials.MaterialFilterOptions = None) -> list[MaterialLibraryObjectType]: + def libraryMaterials(self, libraryName: str, + materialFilter: Materials.MaterialFilter = None, + options: Materials.MaterialFilterOptions = None) -> \ + list[MaterialLibraryObjectType]: """Returns a list of materials managed by this library Each list entry is a tuple containing the UUID, path, and name of the material""" - pass + + @abstractmethod + def libraryFolders(self, libraryName: str) -> list[str]: + """Returns a list of folders managed by this library + + This will return a list of all folders in the library including empty folders""" + + # + # Folder methods + # + + @abstractmethod + def createFolder(self, libraryName: str, path: str) -> None: + """Create a new folder in the given library""" + + @abstractmethod + def renameFolder(self, libraryName: str, oldPath: str, newPath: str) -> None: + """Rename the folder""" + + @abstractmethod + def deleteRecursive(self, libraryName: str, path: str) -> None: + """Delete the folder and all of its contents""" # # Model methods # @abstractmethod - def getModel(self, uuid: str) -> Materials.Model: + def getModel(self, uuid: str) -> ModelObjectType: """Retrieve a model given its UUID""" - pass @abstractmethod - def addModel(self, library: str, path: str, model: Materials.Model) -> None: - """Add a model to a library in the given folder. + def addModel(self, libraryName: str, path: str, model: Materials.Model) -> None: + """Add a model to a library in the given folder. The folder path is relative to + the library and will be created if it doesn't already exist. This will throw a DatabaseModelExistsError exception if the model already exists.""" - pass @abstractmethod - def migrateModel(self, library: str, path: str, model: Materials.Model) -> None: + def migrateModel(self, libraryName: str, path: str, model: Materials.Model) -> None: """Add the model to the library. If the model already exists, then no action is performed.""" - pass @abstractmethod - def updateModel(self, library: str, path: str, model: Materials.Model) -> None: + def updateModel(self, libraryName: str, path: str, model: Materials.Model) -> None: """Update the given model""" - pass @abstractmethod - def setModelPath(self, library: str, path: str, model: Materials.Model) -> None: + def setModelPath(self, libraryName: str, path: str, uuid: str) -> None: """Change the model path within the library""" - pass @abstractmethod - def renameModel(self, library: str, name: str, model: Materials.Model) -> None: + def renameModel(self, libraryName: str, name: str, uuid: str) -> None: """Change the model name""" - pass @abstractmethod - def moveModel(self, library: str, path: str, model: Materials.Model) -> None: + def moveModel(self, libraryName: str, path: str, uuid: str) -> None: """Move a model across libraries Move the model to the desired path in a different library. This should also remove the model from the old library if that library is managed by this interface""" - pass @abstractmethod - def removeModel(self, model: Materials.Model) -> None: + def removeModel(self, uuid: str) -> None: """Remove the model from the library""" - pass # # Material methods # @abstractmethod - def getMaterial(self, uuid: str) -> Materials.Material: + def getMaterial(self, uuid: str) -> MaterialObjectType: """ Retrieve a material given its UUID """ - pass @abstractmethod - def addMaterial(self, library: str, path: str, material: Materials.Material) -> None: - """Add a material to a library in the given folder. + def addMaterial(self, libraryName: str, path: str, material: Materials.Material) -> None: + """Add a material to a library in the given folder. The folder path is relative to + the library and will be created if it doesn't already exist. This will throw a DatabaseMaterialExistsError exception if the model already exists.""" - pass @abstractmethod - def migrateMaterial(self, library: str, path: str, material: Materials.Material) -> None: - """Add the material to the library in the given folder. + def migrateMaterial(self, libraryName: str, path: str, material: Materials.Material) -> None: + """Add the material to the library in the given folder. The folder path is relative to + the library and will be created if it doesn't already exist. If the material already exists, then no action is performed.""" - pass @abstractmethod - def updateMaterial(self, library: str, path: str, material: Materials.Material) -> None: + def updateMaterial(self, libraryName: str, path: str, material: Materials.Material) -> None: """Update the given material""" - pass @abstractmethod - def setMaterialPath(self, library: str, path: str, material: Materials.Material) -> None: + def setMaterialPath(self, libraryName: str, path: str, uuid: str) -> None: """Change the material path within the library""" - pass @abstractmethod - def renameMaterial(self, library: str, name: str, material: Materials.Material) -> None: + def renameMaterial(self, libraryName: str, name: str, uuid: str) -> None: """Change the material name""" - pass @abstractmethod - def moveMaterial(self, library: str, path: str, material: Materials.Material) -> None: + def moveMaterial(self, libraryName: str, path: str, uuid: str) -> None: """Move a material across libraries Move the material to the desired path in a different library. This should also remove the material from the old library if that library is managed by this interface""" - pass @abstractmethod - def removeMaterial(self, material: Materials.Material) -> None: + def removeMaterial(self, uuid: str) -> None: """Remove the material from the library""" - pass diff --git a/src/Mod/Material/App/MaterialLibrary.cpp b/src/Mod/Material/App/MaterialLibrary.cpp index 49c79ede77..40c0b64f6f 100644 --- a/src/Mod/Material/App/MaterialLibrary.cpp +++ b/src/Mod/Material/App/MaterialLibrary.cpp @@ -45,7 +45,6 @@ TYPESYSTEM_SOURCE(Materials::MaterialLibrary, Base::BaseClass) MaterialLibrary::MaterialLibrary(const QString& libraryName, const QString& icon, bool readOnly) : Library(libraryName, icon, readOnly) - , _local(false) {} MaterialLibrary::MaterialLibrary(const QString& libraryName, @@ -53,24 +52,12 @@ MaterialLibrary::MaterialLibrary(const QString& libraryName, const QString& icon, bool readOnly) : Library(libraryName, dir, icon, readOnly) - , _local(false) {} MaterialLibrary::MaterialLibrary(const Library& library) : Library(library) - , _local(false) {} -bool MaterialLibrary::isLocal() const -{ - return _local; -} - -void MaterialLibrary::setLocal(bool local) -{ - _local = local; -} - std::shared_ptr>> MaterialLibrary::getMaterialTree(const std::shared_ptr& filter, const Materials::MaterialFilterOptions& options) const @@ -78,11 +65,11 @@ MaterialLibrary::getMaterialTree(const std::shared_ptr>> materialTree = std::make_shared>>(); - auto materials = MaterialManager::getManager().libraryMaterials(getName(), filter, options); + auto materials = MaterialManager::getManager().libraryMaterials(getName(), filter, options, isLocal()); for (auto& it : *materials) { - auto uuid = std::get<0>(it); - auto path = std::get<1>(it); - auto filename = std::get<2>(it); + auto uuid = it.getUUID(); + auto path = it.getPath(); + auto filename = it.getName(); QStringList list = path.split(QStringLiteral("/")); @@ -98,6 +85,7 @@ MaterialLibrary::getMaterialTree(const std::shared_ptr child = std::make_shared(); child->setFolder(mapPtr); + child->setReadOnly(isReadOnly()); (*node)[itp] = child; node = mapPtr; } @@ -108,6 +96,11 @@ MaterialLibrary::getMaterialTree(const std::shared_ptr child = std::make_shared(); child->setUUID(uuid); + child->setReadOnly(isReadOnly()); + if (isLocal()) { + auto material = MaterialManager::getManager().getMaterial(uuid); + child->setOldFormat(material->isOldFormat()); + } (*node)[filename] = child; } diff --git a/src/Mod/Material/App/MaterialLibrary.h b/src/Mod/Material/App/MaterialLibrary.h index 00d5b51cad..27e78cd8b3 100644 --- a/src/Mod/Material/App/MaterialLibrary.h +++ b/src/Mod/Material/App/MaterialLibrary.h @@ -55,15 +55,12 @@ public: MaterialLibrary(const QString& libraryName, const QString& icon, bool readOnly = true); MaterialLibrary(const QString& libraryName, const QString& dir, - const QString& icon, + const QString& iconPath, bool readOnly = true); MaterialLibrary(const Library& library); MaterialLibrary(const MaterialLibrary&) = delete; ~MaterialLibrary() override = default; - bool isLocal() const; - void setLocal(bool local); - virtual std::shared_ptr>> getMaterialTree(const std::shared_ptr& filter, const Materials::MaterialFilterOptions& options) const; @@ -73,9 +70,6 @@ public: { return shared_from_this(); } - -protected: - bool _local; }; class MaterialsExport MaterialLibraryLocal: public MaterialLibrary @@ -86,7 +80,7 @@ public: MaterialLibraryLocal() = default; MaterialLibraryLocal(const QString& libraryName, const QString& dir, - const QString& icon, + const QString& iconPath, bool readOnly = true); ~MaterialLibraryLocal() override = default; diff --git a/src/Mod/Material/App/MaterialLibrary.pyi b/src/Mod/Material/App/MaterialLibrary.pyi index 300689b02f..badd65e69e 100644 --- a/src/Mod/Material/App/MaterialLibrary.pyi +++ b/src/Mod/Material/App/MaterialLibrary.pyi @@ -19,8 +19,8 @@ class MaterialLibrary(BaseClass): Name: str = ... """Name of the library""" - Icon: str = ... - """String value of the icon.""" + Icon: bytes = ... + """Icon as an array of bytes.""" Directory: str = ... """Local directory where the library is located. For non-local libraries this will be empty""" diff --git a/src/Mod/Material/App/MaterialLibraryPyImp.cpp b/src/Mod/Material/App/MaterialLibraryPyImp.cpp index 58c788a674..7001d9dc6b 100644 --- a/src/Mod/Material/App/MaterialLibraryPyImp.cpp +++ b/src/Mod/Material/App/MaterialLibraryPyImp.cpp @@ -72,15 +72,22 @@ void MaterialLibraryPy::setName(const Py::String value) getMaterialLibraryPtr()->setName(QString::fromStdString(value)); } -Py::String MaterialLibraryPy::getIcon() const +Py::Object MaterialLibraryPy::getIcon() const { - auto path = getMaterialLibraryPtr()->getIconPath(); - return {path.toStdString()}; + auto icon = getMaterialLibraryPtr()->getIcon(); + return Py::Bytes(icon.data(), icon.size()); } -void MaterialLibraryPy::setIcon(const Py::String value) +void MaterialLibraryPy::setIcon(const Py::Object value) { - getMaterialLibraryPtr()->setIconPath(QString::fromStdString(value)); + if (value.isNone()) { + getMaterialLibraryPtr()->setIcon(QByteArray()); + } + else { + auto pyBytes = Py::Bytes(value); + getMaterialLibraryPtr()->setIcon( + QByteArray(pyBytes.as_std_string().data(), pyBytes.size())); + } } Py::String MaterialLibraryPy::getDirectory() const diff --git a/src/Mod/Material/App/MaterialManager.cpp b/src/Mod/Material/App/MaterialManager.cpp index f5ca97b66e..8bcc7c023d 100644 --- a/src/Mod/Material/App/MaterialManager.cpp +++ b/src/Mod/Material/App/MaterialManager.cpp @@ -61,15 +61,21 @@ std::unique_ptr MaterialManager::_externalManager; MaterialManager::MaterialManager() { +#if defined(BUILD_MATERIAL_EXTERNAL) _hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Mod/Material/ExternalInterface"); _useExternal = _hGrp->GetBool("UseExternal", false); _hGrp->Attach(this); +#else + _useExternal = false; +#endif } MaterialManager::~MaterialManager() { +#if defined(BUILD_MATERIAL_EXTERNAL) _hGrp->Detach(this); +#endif } MaterialManager& MaterialManager::getManager() @@ -267,26 +273,49 @@ 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& iconPath, + bool readOnly) { +#if defined(BUILD_MATERIAL_EXTERNAL) + if (_useExternal) { + auto icon = Materials::Library::getIcon(iconPath); + _externalManager->createLibrary(libraryName, icon, readOnly); + return; + } +#endif throw CreationError("Local library requires a path"); } void MaterialManager::createLocalLibrary(const QString& libraryName, const QString& directory, - const QString& icon, + const QString& iconPath, bool readOnly) { - _localManager->createLibrary(libraryName, directory, icon, readOnly); + _localManager->createLibrary(libraryName, directory, iconPath, readOnly); } void MaterialManager::renameLibrary(const QString& libraryName, const QString& newName) { - _localManager->renameLibrary(libraryName, newName); + auto library = getLibrary(libraryName); + if (library) { +#if defined(BUILD_MATERIAL_EXTERNAL) + if (!library->isLocal()) { + if (_useExternal) { + _externalManager->renameLibrary(libraryName, newName); + return; + } + + throw Materials::RenameError(); + } +#endif + _localManager->renameLibrary(libraryName, newName); + } } -void MaterialManager::changeIcon(const QString& libraryName, const QString& icon) +void MaterialManager::changeIcon(const QString& libraryName, const QString& iconPath) { + auto icon = Materials::Library::getIcon(iconPath); _localManager->changeIcon(libraryName, icon); } @@ -295,11 +324,11 @@ void MaterialManager::removeLibrary(const QString& libraryName) _localManager->removeLibrary(libraryName); } -std::shared_ptr>> -MaterialManager::libraryMaterials(const QString& libraryName) +std::shared_ptr> +MaterialManager::libraryMaterials(const QString& libraryName, bool local) { #if defined(BUILD_MATERIAL_EXTERNAL) - if (_useExternal) { + if (_useExternal && !local) { try { auto materials = _externalManager->libraryMaterials(libraryName); if (materials) { @@ -313,13 +342,14 @@ MaterialManager::libraryMaterials(const QString& libraryName) return _localManager->libraryMaterials(libraryName); } -std::shared_ptr>> +std::shared_ptr> MaterialManager::libraryMaterials(const QString& libraryName, const std::shared_ptr& filter, - const MaterialFilterOptions& options) + const MaterialFilterOptions& options, + bool local) { #if defined(BUILD_MATERIAL_EXTERNAL) - if (_useExternal) { + if (_useExternal && !local) { try { auto materials = _externalManager->libraryMaterials(libraryName, filter, options); if (materials) { @@ -383,6 +413,14 @@ void MaterialManager::createFolder(const std::shared_ptr& libra _localManager->createFolder(materialLibrary, path); } +#if defined(BUILD_MATERIAL_EXTERNAL) + else if (_useExternal) { + _externalManager->createFolder(library, path); + } + else { + throw Materials::CreationError("External materials are not enabled"); + } +#endif } void MaterialManager::renameFolder(const std::shared_ptr& library, @@ -395,6 +433,14 @@ void MaterialManager::renameFolder(const std::shared_ptr& libra _localManager->renameFolder(materialLibrary, oldPath, newPath); } +#if defined(BUILD_MATERIAL_EXTERNAL) + else if (_useExternal) { + _externalManager->renameFolder(library, oldPath, newPath); + } + else { + throw Materials::RenameError("External materials are not enabled"); + } +#endif } void MaterialManager::deleteRecursive(const std::shared_ptr& library, @@ -406,6 +452,14 @@ void MaterialManager::deleteRecursive(const std::shared_ptr& li _localManager->deleteRecursive(materialLibrary, path); } +#if defined(BUILD_MATERIAL_EXTERNAL) + else if (_useExternal) { + _externalManager->deleteRecursive(library, path); + } + else { + throw Materials::DeleteError("External materials are not enabled"); + } +#endif } //===== @@ -562,16 +616,22 @@ void MaterialManager::dereference(std::shared_ptr material) const #if defined(BUILD_MATERIAL_EXTERNAL) void MaterialManager::migrateToExternal(const std::shared_ptr& library) { - _externalManager->createLibrary(library->getName(), - library->getIconPath(), - library->isReadOnly()); + try { + _externalManager->createLibrary(library->getName(), + library->getIcon(), + library->isReadOnly()); + } + catch (const CreationError&) { + } + catch (const ConnectionError&) { + } auto materials = _localManager->libraryMaterials(library->getName()); - for (auto& tuple : *materials) { - 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", + for (auto& it : *materials) { + auto uuid = it.getUUID(); + auto path = it.getPath(); + auto name = it.getName(); + Base::Console().log("\t('%s', '%s', '%s')\n", uuid.toStdString().c_str(), path.toStdString().c_str(), name.toStdString().c_str()); @@ -586,11 +646,12 @@ void MaterialManager::migrateToExternal(const std::shared_ptr& library) { auto materials = _localManager->libraryMaterials(library->getName()); - for (auto& tuple : *materials) { - 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", + _externalManager->resetCache(); + for (auto& it : *materials) { + auto uuid = it.getUUID(); + auto path = it.getPath(); + auto name = it.getName(); + Base::Console().log("\t('%s', '%s', '%s')\n", uuid.toStdString().c_str(), path.toStdString().c_str(), name.toStdString().c_str()); diff --git a/src/Mod/Material/App/MaterialManager.h b/src/Mod/Material/App/MaterialManager.h index 409a4e591e..0d2e052a4b 100644 --- a/src/Mod/Material/App/MaterialManager.h +++ b/src/Mod/Material/App/MaterialManager.h @@ -69,23 +69,27 @@ public: static QString defaultMaterialUUID(); // Library management + bool useExternal() const { return _useExternal; } 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 createLibrary(const QString& libraryName, + const QString& iconPath, + bool readOnly = true); void createLocalLibrary(const QString& libraryName, const QString& directory, - const QString& icon, + const QString& iconPath, bool readOnly = true); void renameLibrary(const QString& libraryName, const QString& newName); - void changeIcon(const QString& libraryName, const QString& icon); + void changeIcon(const QString& libraryName, const QString& iconPath); void removeLibrary(const QString& libraryName); - std::shared_ptr>> - libraryMaterials(const QString& libraryName); - std::shared_ptr>> + std::shared_ptr> + libraryMaterials(const QString& libraryName, bool local = false); + std::shared_ptr> libraryMaterials(const QString& libraryName, const std::shared_ptr& filter, - const MaterialFilterOptions& options); + const MaterialFilterOptions& options, + bool local = false); bool isLocalLibrary(const QString& libraryName); // Folder management diff --git a/src/Mod/Material/App/MaterialManagerExternal.cpp b/src/Mod/Material/App/MaterialManagerExternal.cpp index de90466e26..490bb84e11 100644 --- a/src/Mod/Material/App/MaterialManagerExternal.cpp +++ b/src/Mod/Material/App/MaterialManagerExternal.cpp @@ -124,22 +124,40 @@ std::shared_ptr MaterialManagerExternal::getLibrary(const QStri catch (const ConnectionError& e) { throw LibraryNotFound(e.what()); } + catch (...) { + throw LibraryNotFound("Unknown exception"); + } } void MaterialManagerExternal::createLibrary(const QString& libraryName, - const QString& icon, + const QByteArray& icon, bool readOnly) { ExternalManager::getManager()->createLibrary(libraryName, icon, readOnly); } -std::shared_ptr>> +void MaterialManagerExternal::renameLibrary(const QString& libraryName, const QString& newName) +{ + ExternalManager::getManager()->renameLibrary(libraryName, newName); +} + +void MaterialManagerExternal::changeIcon(const QString& libraryName, const QByteArray& icon) +{ + ExternalManager::getManager()->changeIcon(libraryName, icon); +} + +void MaterialManagerExternal::removeLibrary(const QString& libraryName) +{ + ExternalManager::getManager()->removeLibrary(libraryName); +} + +std::shared_ptr> MaterialManagerExternal::libraryMaterials(const QString& libraryName) { return ExternalManager::getManager()->libraryMaterials(libraryName); } -std::shared_ptr>> +std::shared_ptr> MaterialManagerExternal::libraryMaterials(const QString& libraryName, const std::shared_ptr& filter, const MaterialFilterOptions& options) @@ -147,12 +165,44 @@ MaterialManagerExternal::libraryMaterials(const QString& libraryName, return ExternalManager::getManager()->libraryMaterials(libraryName, filter, options); } +//===== +// +// Folder management +// +//===== + +void MaterialManagerExternal::createFolder(const std::shared_ptr& library, + const QString& path) +{ + ExternalManager::getManager()->createFolder(library->getName(), path); +} + +void MaterialManagerExternal::renameFolder(const std::shared_ptr& library, + const QString& oldPath, + const QString& newPath) +{ + ExternalManager::getManager()->renameFolder(library->getName(), oldPath, newPath); +} + +void MaterialManagerExternal::deleteRecursive(const std::shared_ptr& library, + const QString& path) +{ + ExternalManager::getManager()->deleteRecursive(library->getName(), path); +} + //===== // // Material management // //===== +std::shared_ptr MaterialManagerExternal::materialNotFound(const QString& uuid) const +{ + // Setting the cache value to nullptr prevents repeated lookups + _cache.emplace(uuid.toStdString(), nullptr); + return nullptr; +} + std::shared_ptr MaterialManagerExternal::getMaterial(const QString& uuid) const { if (_cache.contains(uuid.toStdString())) { @@ -164,12 +214,13 @@ std::shared_ptr MaterialManagerExternal::getMaterial(const QString& uu return material; } catch (const MaterialNotFound& e) { - _cache.emplace(uuid.toStdString(), nullptr); - return nullptr; + return materialNotFound(uuid); } catch (const ConnectionError& e) { - _cache.emplace(uuid.toStdString(), nullptr); - return nullptr; + return materialNotFound(uuid); + } + catch (...) { + return materialNotFound(uuid); } } diff --git a/src/Mod/Material/App/MaterialManagerExternal.h b/src/Mod/Material/App/MaterialManagerExternal.h index 54fc4c9799..870a0f7a4b 100644 --- a/src/Mod/Material/App/MaterialManagerExternal.h +++ b/src/Mod/Material/App/MaterialManagerExternal.h @@ -42,6 +42,7 @@ class Material; namespace Materials { +class LibraryObject; class MaterialLibrary; class MaterialLibraryExternal; class MaterialFilter; @@ -64,15 +65,25 @@ public: std::shared_ptr>> getLibraries(); std::shared_ptr>> getMaterialLibraries(); std::shared_ptr getLibrary(const QString& name) const; - void createLibrary(const QString& libraryName, const QString& icon, bool readOnly = true); - std::shared_ptr>> + void createLibrary(const QString& libraryName, + const QByteArray& icon, + bool readOnly = true); + void renameLibrary(const QString& libraryName, const QString& newName); + void changeIcon(const QString& libraryName, const QByteArray& icon); + void removeLibrary(const QString& libraryName); + std::shared_ptr> libraryMaterials(const QString& libraryName); - std::shared_ptr>> + std::shared_ptr> libraryMaterials(const QString& libraryName, const std::shared_ptr& filter, const MaterialFilterOptions& options); // Folder management + void createFolder(const std::shared_ptr& library, const QString& path); + void renameFolder(const std::shared_ptr& library, + const QString& oldPath, + const QString& newPath); + void deleteRecursive(const std::shared_ptr& library, const QString& path); // Material management std::shared_ptr getMaterial(const QString& uuid) const; @@ -89,6 +100,7 @@ public: private: static void initCache(); + std::shared_ptr materialNotFound(const QString& uuid) const; static QMutex _mutex; diff --git a/src/Mod/Material/App/MaterialManagerLocal.cpp b/src/Mod/Material/App/MaterialManagerLocal.cpp index 3c96b905ce..ad5074c511 100644 --- a/src/Mod/Material/App/MaterialManagerLocal.cpp +++ b/src/Mod/Material/App/MaterialManagerLocal.cpp @@ -140,7 +140,7 @@ std::shared_ptr MaterialManagerLocal::getLibrary(const QString& void MaterialManagerLocal::createLibrary(const QString& libraryName, const QString& directory, - const QString& icon, + const QString& iconPath, bool readOnly) { QDir dir; @@ -151,7 +151,7 @@ void MaterialManagerLocal::createLibrary(const QString& libraryName, } auto materialLibrary = - std::make_shared(libraryName, directory, icon, readOnly); + std::make_shared(libraryName, directory, iconPath, readOnly); _libraryList->push_back(materialLibrary); // This needs to be persisted somehow @@ -171,13 +171,13 @@ void MaterialManagerLocal::renameLibrary(const QString& libraryName, const QStri throw LibraryNotFound(); } -void MaterialManagerLocal::changeIcon(const QString& libraryName, const QString& icon) +void MaterialManagerLocal::changeIcon(const QString& libraryName, const QByteArray& icon) { for (auto& library : *_libraryList) { if (library->isLocal() && library->isName(libraryName)) { auto materialLibrary = reinterpret_cast&>(library); - materialLibrary->setIconPath(icon); + materialLibrary->setIcon(icon); return; } } @@ -199,18 +199,17 @@ void MaterialManagerLocal::removeLibrary(const QString& libraryName) throw LibraryNotFound(); } -std::shared_ptr>> +std::shared_ptr> MaterialManagerLocal::libraryMaterials(const QString& libraryName) { - auto materials = std::make_shared>>(); + auto materials = std::make_shared>(); for (auto& it : *_materialMap) { // This is needed to resolve cyclic dependencies auto library = it.second->getLibrary(); if (library->isName(libraryName)) { - materials->push_back(std::tuple(it.first, - it.second->getDirectory(), - it.second->getName())); + materials->push_back( + LibraryObject(it.first, it.second->getDirectory(), it.second->getName())); } } @@ -235,21 +234,20 @@ bool MaterialManagerLocal::passFilter(const std::shared_ptr& material, return filter->modelIncluded(material); } -std::shared_ptr>> +std::shared_ptr> MaterialManagerLocal::libraryMaterials(const QString& libraryName, const std::shared_ptr& filter, const MaterialFilterOptions& options) { - auto materials = std::make_shared>>(); + auto materials = std::make_shared>(); for (auto& it : *_materialMap) { // This is needed to resolve cyclic dependencies auto library = it.second->getLibrary(); if (library->isName(libraryName)) { if (passFilter(it.second, filter, options)) { - materials->push_back(std::tuple(it.first, - it.second->getDirectory(), - it.second->getName())); + materials->push_back( + LibraryObject(it.first, it.second->getDirectory(), it.second->getName())); } } } diff --git a/src/Mod/Material/App/MaterialManagerLocal.h b/src/Mod/Material/App/MaterialManagerLocal.h index ffb0cf2147..5e37dd8b4c 100644 --- a/src/Mod/Material/App/MaterialManagerLocal.h +++ b/src/Mod/Material/App/MaterialManagerLocal.h @@ -43,6 +43,7 @@ class Material; namespace Materials { +class LibraryObject; class MaterialLibrary; class MaterialLibraryLocal; class MaterialFilter; @@ -65,14 +66,14 @@ public: std::shared_ptr getLibrary(const QString& name) const; void createLibrary(const QString& libraryName, const QString& directory, - const QString& icon, + const QString& iconPath, bool readOnly = true); void renameLibrary(const QString& libraryName, const QString& newName); - void changeIcon(const QString& libraryName, const QString& icon); + void changeIcon(const QString& libraryName, const QByteArray& icon); void removeLibrary(const QString& libraryName); - std::shared_ptr>> + std::shared_ptr> libraryMaterials(const QString& libraryName); - std::shared_ptr>> + std::shared_ptr> libraryMaterials(const QString& libraryName, const std::shared_ptr& filter, const MaterialFilterOptions& options); diff --git a/src/Mod/Material/App/MaterialManagerPyImp.cpp b/src/Mod/Material/App/MaterialManagerPyImp.cpp index 5b2fe808b2..1ae4662f5c 100644 --- a/src/Mod/Material/App/MaterialManagerPyImp.cpp +++ b/src/Mod/Material/App/MaterialManagerPyImp.cpp @@ -147,13 +147,15 @@ Py::List MaterialManagerPy::getMaterialLibraries() const reinterpret_cast&>(lib); libTuple.setItem(0, Py::String(materialLibrary->getName().toStdString())); libTuple.setItem(1, Py::String(materialLibrary->getDirectoryPath().toStdString())); - libTuple.setItem(2, Py::String(materialLibrary->getIconPath().toStdString())); + libTuple.setItem(2, + Py::Bytes(Py::Bytes(materialLibrary->getIcon().data(), + materialLibrary->getIcon().size()))); } else { libTuple.setItem(0, Py::String()); libTuple.setItem(1, Py::String()); - libTuple.setItem(2, Py::String()); + libTuple.setItem(2, Py::Bytes()); } list.append(libTuple); diff --git a/src/Mod/Material/App/MaterialPyImp.cpp b/src/Mod/Material/App/MaterialPyImp.cpp index cc33e4040b..66ae0479e7 100644 --- a/src/Mod/Material/App/MaterialPyImp.cpp +++ b/src/Mod/Material/App/MaterialPyImp.cpp @@ -87,15 +87,19 @@ Py::String MaterialPy::getLibraryRoot() const return ""; } -Py::String MaterialPy::getLibraryIcon() const +Py::Object MaterialPy::getLibraryIcon() const { auto library = getMaterialPtr()->getLibrary(); if (library->isLocal()) { auto materialLibrary = reinterpret_cast&>(library); - return {materialLibrary ? materialLibrary->getIconPath().toStdString() : ""}; + auto icon = materialLibrary->getIcon(); + if (icon.isNull()) { + return Py::Bytes(); + } + return Py::Bytes(icon.data(), icon.size()); } - return ""; + return Py::Bytes(); } Py::String MaterialPy::getName() const diff --git a/src/Mod/Material/App/Model.pyi b/src/Mod/Material/App/Model.pyi index ed88aa0e8f..f004c64eb6 100644 --- a/src/Mod/Material/App/Model.pyi +++ b/src/Mod/Material/App/Model.pyi @@ -22,8 +22,8 @@ class Model(BaseClass): LibraryRoot: Final[str] = "" """Model library path.""" - LibraryIcon: Final[str] = "" - """Model icon path.""" + LibraryIcon: Final[bytes] = "" + """Model icon.""" Name: str = "" """Model name.""" diff --git a/src/Mod/Material/App/ModelLibrary.cpp b/src/Mod/Material/App/ModelLibrary.cpp index a1346ae25c..ecbd09f755 100644 --- a/src/Mod/Material/App/ModelLibrary.cpp +++ b/src/Mod/Material/App/ModelLibrary.cpp @@ -41,32 +41,19 @@ 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, + const QString& iconPath, bool readOnly) - : Library(libraryName, dir, icon, readOnly) - , _local(false) + : Library(libraryName, dir, iconPath, readOnly) {} 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 { @@ -75,9 +62,9 @@ ModelLibrary::getModelTree(ModelFilter filter) const 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 uuid = it.getUUID(); + auto path = it.getPath(); + auto filename = it.getName(); auto model = ModelManager::getManager().getModel(getName(), uuid); if (ModelManager::passFilter(filter, model->getType())) { @@ -121,9 +108,9 @@ ModelLibraryLocal::ModelLibraryLocal(const Library& other) ModelLibraryLocal::ModelLibraryLocal(const QString& libraryName, const QString& dir, - const QString& icon, + const QString& iconPath, bool readOnly) - : ModelLibrary(libraryName, dir, icon, readOnly) + : ModelLibrary(libraryName, dir, iconPath, readOnly) { setLocal(true); diff --git a/src/Mod/Material/App/ModelLibrary.h b/src/Mod/Material/App/ModelLibrary.h index e23e30484c..f1118b4079 100644 --- a/src/Mod/Material/App/ModelLibrary.h +++ b/src/Mod/Material/App/ModelLibrary.h @@ -49,14 +49,11 @@ public: ModelLibrary(const Library& library); ModelLibrary(const QString& libraryName, const QString& dir, - const QString& icon, + const QString& iconPath, 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; @@ -65,9 +62,6 @@ public: { return shared_from_this(); } - -private: - bool _local; }; class MaterialsExport ModelLibraryLocal: public ModelLibrary @@ -78,9 +72,9 @@ public: ModelLibraryLocal(); ModelLibraryLocal(const Library& other); ModelLibraryLocal(const QString& libraryName, - const QString& dir, - const QString& icon, - bool readOnly = true); + const QString& dir, + const QString& iconPath, + bool readOnly = true); ModelLibraryLocal(const ModelLibraryLocal& other) = delete; ~ModelLibraryLocal() override = default; diff --git a/src/Mod/Material/App/ModelManager.cpp b/src/Mod/Material/App/ModelManager.cpp index e7857f57f4..e103c43b5f 100644 --- a/src/Mod/Material/App/ModelManager.cpp +++ b/src/Mod/Material/App/ModelManager.cpp @@ -158,10 +158,12 @@ std::shared_ptr>> ModelManager::getLocal return _localManager->getLibraries(); } -void ModelManager::createLibrary([[maybe_unused]] const QString& libraryName, [[maybe_unused]] const QString& icon, - [[maybe_unused]] bool readOnly) +void ModelManager::createLibrary([[maybe_unused]] const QString& libraryName, + [[maybe_unused]] const QString& iconPath, + [[maybe_unused]] bool readOnly) { #if defined(BUILD_MATERIAL_EXTERNAL) + auto icon = Materials::Library::getIcon(iconPath); _externalManager->createLibrary(libraryName, icon, readOnly); #endif } @@ -202,7 +204,7 @@ void ModelManager::removeLibrary(const QString& libraryName) _localManager->removeLibrary(libraryName); } -std::shared_ptr>> +std::shared_ptr> ModelManager::libraryModels(const QString& libraryName) { #if defined(BUILD_MATERIAL_EXTERNAL) @@ -319,16 +321,22 @@ bool ModelManager::passFilter(ModelFilter filter, Model::ModelType modelType) #if defined(BUILD_MATERIAL_EXTERNAL) void ModelManager::migrateToExternal(const std::shared_ptr& library) { - _externalManager->createLibrary(library->getName(), - library->getIconPath(), - library->isReadOnly()); + try { + _externalManager->createLibrary(library->getName(), + library->getIcon(), + library->isReadOnly()); + } + catch (const CreationError&) { + } + catch (const ConnectionError&) { + } 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", + for (auto& it : *models) { + auto uuid = it.getUUID(); + auto path = it.getPath(); + auto name = it.getName(); + Base::Console().log("\t('%s', '%s', '%s')\n", uuid.toStdString().c_str(), path.toStdString().c_str(), name.toStdString().c_str()); @@ -341,11 +349,11 @@ void ModelManager::migrateToExternal(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", + for (auto& it : *models) { + auto uuid = it.getUUID(); + auto path = it.getPath(); + auto name = it.getName(); + Base::Console().log("\t('%s', '%s', '%s')\n", uuid.toStdString().c_str(), path.toStdString().c_str(), name.toStdString().c_str()); diff --git a/src/Mod/Material/App/ModelManager.h b/src/Mod/Material/App/ModelManager.h index 81cf39a3ee..42514ea4e5 100644 --- a/src/Mod/Material/App/ModelManager.h +++ b/src/Mod/Material/App/ModelManager.h @@ -55,7 +55,9 @@ public: 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 createLibrary(const QString& libraryName, + const QString& iconPath, + bool readOnly = true); void createLocalLibrary(const QString& libraryName, const QString& directory, const QString& icon, @@ -63,7 +65,7 @@ public: void renameLibrary(const QString& libraryName, const QString& newName); void changeIcon(const QString& libraryName, const QString& icon); void removeLibrary(const QString& libraryName); - std::shared_ptr>> + std::shared_ptr> libraryModels(const QString& libraryName); bool isLocalLibrary(const QString& libraryName); diff --git a/src/Mod/Material/App/ModelManagerExternal.cpp b/src/Mod/Material/App/ModelManagerExternal.cpp index 44b68adfcd..f58d14a091 100644 --- a/src/Mod/Material/App/ModelManagerExternal.cpp +++ b/src/Mod/Material/App/ModelManagerExternal.cpp @@ -102,16 +102,19 @@ std::shared_ptr ModelManagerExternal::getLibrary(const QString& na catch (const ConnectionError& e) { throw LibraryNotFound(e.what()); } + catch (...) { + throw LibraryNotFound("Unknown exception"); + } } void ModelManagerExternal::createLibrary(const QString& libraryName, - const QString& icon, - bool readOnly) + const QByteArray& icon, + bool readOnly) { ExternalManager::getManager()->createLibrary(libraryName, icon, readOnly); } -std::shared_ptr>> +std::shared_ptr> ModelManagerExternal::libraryModels(const QString& libraryName) { return ExternalManager::getManager()->libraryModels(libraryName); @@ -123,6 +126,13 @@ ModelManagerExternal::libraryModels(const QString& libraryName) // //===== +std::shared_ptr ModelManagerExternal::modelNotFound(const QString& uuid) +{ + // Setting the cache value to nullptr prevents repeated lookups + _cache.emplace(uuid.toStdString(), nullptr); + return nullptr; +} + std::shared_ptr ModelManagerExternal::getModel(const QString& uuid) { if (_cache.contains(uuid.toStdString())) { @@ -135,12 +145,13 @@ std::shared_ptr ModelManagerExternal::getModel(const QString& uuid) return model; } catch (const ModelNotFound& e) { - _cache.emplace(uuid.toStdString(), nullptr); - return nullptr; + return modelNotFound(uuid); } catch (const ConnectionError& e) { - _cache.emplace(uuid.toStdString(), nullptr); - return nullptr; + return modelNotFound(uuid); + } + catch (...) { + return modelNotFound(uuid); } } diff --git a/src/Mod/Material/App/ModelManagerExternal.h b/src/Mod/Material/App/ModelManagerExternal.h index e99a7d9c4d..32514967bd 100644 --- a/src/Mod/Material/App/ModelManagerExternal.h +++ b/src/Mod/Material/App/ModelManagerExternal.h @@ -54,9 +54,9 @@ public: std::shared_ptr>> getLibraries(); std::shared_ptr getLibrary(const QString& name) const; void createLibrary(const QString& libraryName, - const QString& icon, + const QByteArray& icon, bool readOnly = true); - std::shared_ptr>> + std::shared_ptr> libraryModels(const QString& libraryName); // Model management @@ -73,6 +73,7 @@ public: private: static void initCache(); + std::shared_ptr modelNotFound(const QString& uuid); static QMutex _mutex; diff --git a/src/Mod/Material/App/ModelManagerLocal.cpp b/src/Mod/Material/App/ModelManagerLocal.cpp index 0890c333f2..9f59ad4657 100644 --- a/src/Mod/Material/App/ModelManagerLocal.cpp +++ b/src/Mod/Material/App/ModelManagerLocal.cpp @@ -134,7 +134,7 @@ void ModelManagerLocal::changeIcon(const QString& libraryName, const QString& ic { for (auto& library : *_libraryList) { if (library->isName(libraryName)) { - library->setIconPath(icon); + library->setIcon(icon); return; } } @@ -156,16 +156,16 @@ void ModelManagerLocal::removeLibrary(const QString& libraryName) throw LibraryNotFound(); } -std::shared_ptr>> +std::shared_ptr> ModelManagerLocal::libraryModels(const QString& libraryName) { - auto models = std::make_shared>>(); + auto models = std::make_shared>(); for (auto& it : *_modelMap) { // This is needed to resolve cyclic dependencies if (it.second->getLibrary()->isName(libraryName)) { models->push_back( - std::tuple(it.first, it.second->getDirectory(), it.second->getName())); + LibraryObject(it.first, it.second->getDirectory(), it.second->getName())); } } diff --git a/src/Mod/Material/App/ModelManagerLocal.h b/src/Mod/Material/App/ModelManagerLocal.h index 824110ec91..8a1c960bba 100644 --- a/src/Mod/Material/App/ModelManagerLocal.h +++ b/src/Mod/Material/App/ModelManagerLocal.h @@ -55,7 +55,7 @@ public: void renameLibrary(const QString& libraryName, const QString& newName); void changeIcon(const QString& libraryName, const QString& icon); void removeLibrary(const QString& libraryName); - std::shared_ptr>> + std::shared_ptr> libraryModels(const QString& libraryName); std::shared_ptr>> getModels() diff --git a/src/Mod/Material/App/ModelManagerPyImp.cpp b/src/Mod/Material/App/ModelManagerPyImp.cpp index 3dd8d6467d..1aa23a6c2e 100644 --- a/src/Mod/Material/App/ModelManagerPyImp.cpp +++ b/src/Mod/Material/App/ModelManagerPyImp.cpp @@ -124,7 +124,7 @@ Py::List ModelManagerPy::getModelLibraries() const Py::Tuple libTuple(3); libTuple.setItem(0, Py::String(lib->getName().toStdString())); libTuple.setItem(1, Py::String(lib->getDirectoryPath().toStdString())); - libTuple.setItem(2, Py::String(lib->getIconPath().toStdString())); + libTuple.setItem(2, Py::Bytes(lib->getIcon().data(), lib->getIcon().size())); libTuple.setItem(3, Py::Boolean(lib->isReadOnly())); list.append(libTuple); @@ -143,7 +143,7 @@ Py::List ModelManagerPy::getLocalModelLibraries() const Py::Tuple libTuple(3); libTuple.setItem(0, Py::String(lib->getName().toStdString())); libTuple.setItem(1, Py::String(lib->getDirectoryPath().toStdString())); - libTuple.setItem(2, Py::String(lib->getIconPath().toStdString())); + libTuple.setItem(2, Py::Bytes(lib->getIcon().data(), lib->getIcon().size())); libTuple.setItem(3, Py::Boolean(lib->isReadOnly())); list.append(libTuple); diff --git a/src/Mod/Material/App/ModelPyImp.cpp b/src/Mod/Material/App/ModelPyImp.cpp index f90abf64bb..a9838ed649 100644 --- a/src/Mod/Material/App/ModelPyImp.cpp +++ b/src/Mod/Material/App/ModelPyImp.cpp @@ -61,13 +61,16 @@ Py::String ModelPy::getLibraryName() const Py::String ModelPy::getLibraryRoot() const { auto library = getModelPtr()->getLibrary(); + if (!library->isLocal()) { + return ""; + } return Py::String(library ? library->getDirectoryPath().toStdString() : ""); } -Py::String ModelPy::getLibraryIcon() const +Py::Object ModelPy::getLibraryIcon() const { auto library = getModelPtr()->getLibrary(); - return Py::String(library ? library->getIconPath().toStdString() : ""); + return Py::Bytes(library->getIcon().data(), library->getIcon().size()); } Py::String ModelPy::getName() const diff --git a/src/Mod/Material/Gui/MaterialSave.cpp b/src/Mod/Material/Gui/MaterialSave.cpp index 6674baeae7..645b9f9679 100644 --- a/src/Mod/Material/Gui/MaterialSave.cpp +++ b/src/Mod/Material/Gui/MaterialSave.cpp @@ -32,6 +32,7 @@ #include +#include "MaterialsEditor.h" #include "MaterialSave.h" #include "ui_MaterialSave.h" @@ -360,7 +361,7 @@ void MaterialSave::showSelectedTree() if (ui->comboLibrary->count() > 0) { auto variant = ui->comboLibrary->currentData(); auto library = variant.value>(); - QIcon icon(library->getIconPath()); + auto icon = MaterialsEditor::getIcon(library); QIcon folderIcon(QStringLiteral(":/icons/folder.svg")); _libraryName = library->getName(); _selectedPath = QStringLiteral("/") + _libraryName; diff --git a/src/Mod/Material/Gui/MaterialTreeWidget.cpp b/src/Mod/Material/Gui/MaterialTreeWidget.cpp index 0c7519667e..59034f9e04 100644 --- a/src/Mod/Material/Gui/MaterialTreeWidget.cpp +++ b/src/Mod/Material/Gui/MaterialTreeWidget.cpp @@ -572,7 +572,7 @@ void MaterialTreeWidget::fillMaterialTree() lib->setFlags(Qt::ItemIsEnabled); addExpanded(model, lib, param); - QIcon icon(library->getIconPath()); + auto icon = MaterialsEditor::getIcon(library); QIcon folderIcon(QStringLiteral(":/icons/folder.svg")); addMaterials(*lib, materialTree, folderIcon, icon, param); @@ -619,7 +619,7 @@ void MaterialTreeWidget::addRecents(QStandardItem* parent) for (auto& uuid : _recents) { try { auto material = getMaterialManager().getMaterial(uuid); - QIcon icon(material->getLibrary()->getIconPath()); + auto icon = MaterialsEditor::getIcon(material->getLibrary()); auto card = new QStandardItem(icon, material->getName()); card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); card->setData(QVariant(uuid), Qt::UserRole); @@ -636,7 +636,7 @@ void MaterialTreeWidget::addFavorites(QStandardItem* parent) for (auto& uuid : _favorites) { try { auto material = getMaterialManager().getMaterial(uuid); - QIcon icon(material->getLibrary()->getIconPath()); + auto icon = MaterialsEditor::getIcon(material->getLibrary()); auto card = new QStandardItem(icon, material->getName()); card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); card->setData(QVariant(uuid), Qt::UserRole); diff --git a/src/Mod/Material/Gui/MaterialsEditor.cpp b/src/Mod/Material/Gui/MaterialsEditor.cpp index 51302dbd8b..f384083f29 100644 --- a/src/Mod/Material/Gui/MaterialsEditor.cpp +++ b/src/Mod/Material/Gui/MaterialsEditor.cpp @@ -801,6 +801,33 @@ void MaterialsEditor::createAppearanceTree() connect(delegate, &MaterialDelegate::propertyChange, this, &MaterialsEditor::propertyChange); } +QIcon MaterialsEditor::getIcon(const std::shared_ptr& library) +{ + // Load from the QByteArray if available + QIcon icon; + if (library->hasIcon()) { + QImage image; + if (!image.loadFromData(library->getIcon())) { + Base::Console().log("Unable to load icon image for library '%s'\n", + library->getName().toStdString().c_str()); + return QIcon(); + } + icon = QIcon(QPixmap::fromImage(image)); + } + + return icon; +} + +QIcon MaterialsEditor::getIcon(const std::shared_ptr& library) +{ + return getIcon(std::static_pointer_cast(library)); +} + +QIcon MaterialsEditor::getIcon(const std::shared_ptr& library) +{ + return getIcon(std::static_pointer_cast(library)); +} + void MaterialsEditor::addRecents(QStandardItem* parent) { auto tree = ui->treeMaterials; @@ -808,13 +835,13 @@ void MaterialsEditor::addRecents(QStandardItem* parent) try { auto material = getMaterialManager().getMaterial(uuid); // if (material->getLibrary()->isLocal()) { - QIcon icon = QIcon(material->getLibrary()->getIconPath()); - auto card = new QStandardItem(icon, libraryPath(material)); - card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled - | Qt::ItemIsDropEnabled); - card->setData(QVariant(uuid), Qt::UserRole); + QIcon icon = getIcon(material->getLibrary()); + auto card = new QStandardItem(icon, libraryPath(material)); + card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled + | Qt::ItemIsDropEnabled); + card->setData(QVariant(uuid), Qt::UserRole); - addExpanded(tree, parent, card); + addExpanded(tree, parent, card); // } } catch (const Materials::MaterialNotFound&) { @@ -828,7 +855,7 @@ void MaterialsEditor::addFavorites(QStandardItem* parent) for (auto& uuid : _favorites) { try { auto material = getMaterialManager().getMaterial(uuid); - QIcon icon = QIcon(material->getLibrary()->getIconPath()); + QIcon icon = getIcon(material->getLibrary()); auto card = new QStandardItem(icon, libraryPath(material)); card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled); @@ -877,7 +904,7 @@ void MaterialsEditor::fillMaterialTree() lib->setFlags(Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled); addExpanded(tree, model, lib, param); - QIcon icon(library->getIconPath()); + QIcon icon = getIcon(library); QIcon folderIcon(QStringLiteral(":/icons/folder.svg")); addMaterials(*lib, materialTree, folderIcon, icon, param); diff --git a/src/Mod/Material/Gui/MaterialsEditor.h b/src/Mod/Material/Gui/MaterialsEditor.h index da523c850b..18e63b5b27 100644 --- a/src/Mod/Material/Gui/MaterialsEditor.h +++ b/src/Mod/Material/Gui/MaterialsEditor.h @@ -87,6 +87,10 @@ public: static QString libraryPath(const std::shared_ptr& material); + static QIcon getIcon(const std::shared_ptr& library); + static QIcon getIcon(const std::shared_ptr& library); + static QIcon getIcon(const std::shared_ptr& library); + void updateMaterialAppearance(); void updateMaterialProperties(); void updateMaterialGeneral(); diff --git a/src/Mod/Material/Gui/ModelSelect.cpp b/src/Mod/Material/Gui/ModelSelect.cpp index d2d7723824..cc4a81b575 100644 --- a/src/Mod/Material/Gui/ModelSelect.cpp +++ b/src/Mod/Material/Gui/ModelSelect.cpp @@ -33,6 +33,7 @@ #include #include +#include "MaterialsEditor.h" #include "ModelSelect.h" #include "ui_ModelSelect.h" @@ -272,7 +273,7 @@ void ModelSelect::addRecents(QStandardItem* parent) auto model = Materials::ModelManager::getManager().getModel(uuid); if (Materials::ModelManager::getManager().passFilter(_filter, model->getType())) { - QIcon icon = QIcon(model->getLibrary()->getIconPath()); + auto icon = MaterialsEditor::getIcon(model->getLibrary()); auto card = new QStandardItem(icon, model->getName()); card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled); @@ -294,7 +295,7 @@ void ModelSelect::addFavorites(QStandardItem* parent) auto model = Materials::ModelManager::getManager().getModel(uuid); if (Materials::ModelManager::getManager().passFilter(_filter, model->getType())) { - QIcon icon = QIcon(model->getLibrary()->getIconPath()); + auto icon = MaterialsEditor::getIcon(model->getLibrary()); auto card = new QStandardItem(icon, model->getName()); card->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled); @@ -350,7 +351,7 @@ void ModelSelect::fillTree() addExpanded(tree, model, lib); auto modelTree = Materials::ModelManager::getManager().getModelTree(library, _filter); - addModels(*lib, modelTree, QIcon(library->getIconPath())); + addModels(*lib, modelTree, MaterialsEditor::getIcon(library)); } } diff --git a/src/Mod/Material/Gui/TaskMigrateExternal.cpp b/src/Mod/Material/Gui/TaskMigrateExternal.cpp index d0af66897d..63319e4fe3 100644 --- a/src/Mod/Material/Gui/TaskMigrateExternal.cpp +++ b/src/Mod/Material/Gui/TaskMigrateExternal.cpp @@ -153,7 +153,7 @@ void DlgMigrateExternal::migrate() void DlgMigrateExternal::statusUpdate(const QString& status) { - Base::Console().Log("%s\n", status.toStdString().c_str()); + Base::Console().log("%s\n", status.toStdString().c_str()); ui->textStatus->append(status); // This is required to update in real time