// SPDX-License-Identifier: LGPL-2.1-or-later /*************************************************************************** * Copyright (c) 2023 David Carter * * * * This file is part of FreeCAD. * * * * FreeCAD is free software: you can redistribute it and/or modify it * * under the terms of the GNU Lesser General Public License as * * published by the Free Software Foundation, either version 2.1 of the * * License, or (at your option) any later version. * * * * FreeCAD is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * Lesser General Public License for more details. * * * * You should have received a copy of the GNU Lesser General Public * * License along with FreeCAD. If not, see * * . * * * **************************************************************************/ #include #include #include #include #include "MaterialFilter.h" #include "MaterialLibrary.h" #include "MaterialLoader.h" #include "MaterialManager.h" #include "Materials.h" #include "ModelManager.h" using namespace Materials; /* TRANSLATOR Material::Materials */ TYPESYSTEM_SOURCE(Materials::MaterialLibrary, Base::BaseClass) MaterialLibrary::MaterialLibrary(const QString& libraryName, const QString& icon, bool readOnly) : Library(libraryName, icon, readOnly) {} MaterialLibrary::MaterialLibrary(const QString& libraryName, const QString& dir, const QString& icon, bool readOnly) : Library(libraryName, dir, icon, readOnly) {} MaterialLibrary::MaterialLibrary(const Library& library) : Library(library) {} std::shared_ptr>> MaterialLibrary::getMaterialTree(const Materials::MaterialFilter& filter, const Materials::MaterialFilterOptions& options) const { std::shared_ptr>> materialTree = std::make_shared>>(); auto materials = MaterialManager::getManager().libraryMaterials(getName(), filter, options, isLocal()); for (auto& it : *materials) { auto uuid = it.getUUID(); auto path = it.getPath(); auto filename = it.getName(); QStringList list = path.split(QStringLiteral("/")); // Start at the root std::shared_ptr>> node = materialTree; for (auto& itp : list) { if (!itp.isEmpty()) { // Add the folder only if it's not already there if (!node->contains(itp)) { auto mapPtr = std::make_shared< std::map>>(); std::shared_ptr child = std::make_shared(); child->setFolder(mapPtr); child->setReadOnly(isReadOnly()); (*node)[itp] = child; node = mapPtr; } else { node = (*node)[itp]->getFolder(); } } } 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; } // // Empty folders aren't included in _materialPathMap, so we add them by looking at the file // // system // if (!filter || options.includeEmptyFolders()) { // if (isLocal()) { // auto& materialLibrary = // *(reinterpret_cast(this)); // auto folderList = MaterialLoader::getMaterialFolders(materialLibrary); // for (auto& folder : *folderList) { // QStringList list = folder.split(QStringLiteral("/")); // // Start at the root // auto node = materialTree; // for (auto& itp : list) { // // Add the folder only if it's not already there // if (!node->contains(itp)) { // std::shared_ptr>> // mapPtr = std::make_shared< // std::map>>(); // std::shared_ptr child = // std::make_shared(); // child->setFolder(mapPtr); // (*node)[itp] = child; // node = mapPtr; // } // else { // node = (*node)[itp]->getFolder(); // } // } // } // } // } return materialTree; } /* TRANSLATOR Material::Materials */ TYPESYSTEM_SOURCE(Materials::MaterialLibraryLocal, Materials::MaterialLibrary) MaterialLibraryLocal::MaterialLibraryLocal(const QString& libraryName, const QString& dir, const QString& icon, bool readOnly) : MaterialLibrary(libraryName, dir, icon, readOnly) , _materialPathMap(std::make_unique>>()) { setLocal(true); } void MaterialLibraryLocal::createFolder(const QString& path) { QString filePath = getLocalPath(path); QDir fileDir(filePath); if (!fileDir.exists()) { if (!fileDir.mkpath(filePath)) { Base::Console().error("Unable to create directory path '%s'\n", filePath.toStdString().c_str()); } } } void MaterialLibraryLocal::renameFolder(const QString& oldPath, const QString& newPath) { QString filePath = getLocalPath(oldPath); QString newFilePath = getLocalPath(newPath); QDir fileDir(filePath); if (fileDir.exists()) { if (!fileDir.rename(filePath, newFilePath)) { Base::Console().error("Unable to rename directory path '%s'\n", filePath.toStdString().c_str()); } } updatePaths(oldPath, newPath); } void MaterialLibraryLocal::deleteRecursive(const QString& path) { if (isRoot(path)) { return; } QString filePath = getLocalPath(path); auto& manager = MaterialManager::getManager(); QFileInfo info(filePath); if (info.isDir()) { deleteDir(manager, filePath); } else { deleteFile(manager, filePath); } } // This accepts the filesystem path as returned from getLocalPath void MaterialLibraryLocal::deleteDir(MaterialManager& manager, const QString& path) { // Remove the children first QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); // Add paths to a list so there are no iterator errors QVector dirList; QVector fileList; while (it.hasNext()) { auto pathname = it.next(); QFileInfo file(pathname); if (file.isFile()) { fileList.push_back(pathname); } else if (file.isDir()) { dirList.push_back(pathname); } } // Remove the subdirs first while (!dirList.isEmpty()) { QString dirPath = dirList.takeFirst(); deleteDir(manager, dirPath); } // Remove the files while (!fileList.isEmpty()) { QString filePath = fileList.takeFirst(); deleteFile(manager, filePath); } // Finally, remove ourself QDir dir; if (!dir.rmdir(path)) { throw DeleteError(path); } } // This accepts the filesystem path as returned from getLocalPath void MaterialLibraryLocal::deleteFile(MaterialManager& manager, const QString& path) { if (QFile::remove(path)) { // Remove from the map QString rPath = getRelativePath(path); try { auto material = getMaterialByPath(rPath); manager.remove(material->getUUID()); } catch (const MaterialNotFound&) { Base::Console().log("Unable to remove file from materials list\n"); } _materialPathMap->erase(rPath); } else { QString error = QStringLiteral("DeleteError: Unable to delete ") + path; throw DeleteError(error); } } void MaterialLibraryLocal::updatePaths(const QString& oldPath, const QString& newPath) { // Update the path map QString op = getRelativePath(oldPath); QString np = getRelativePath(newPath); std::unique_ptr>> pathMap = std::make_unique>>(); for (auto& itp : *_materialPathMap) { QString path = itp.first; if (path.startsWith(op)) { path = np + path.remove(0, op.size()); } itp.second->setDirectory(path); (*pathMap)[path] = itp.second; } _materialPathMap = std::move(pathMap); } std::shared_ptr MaterialLibraryLocal::saveMaterial(const std::shared_ptr& material, const QString& path, bool overwrite, bool saveAsCopy, bool saveInherited) { QString filePath = getLocalPath(path); QFile file(filePath); QFileInfo info(file); QDir fileDir(info.path()); if (!fileDir.exists()) { if (!fileDir.mkpath(info.path())) { Base::Console().error("Unable to create directory path '%s'\n", info.path().toStdString().c_str()); } } if (info.exists()) { if (!overwrite) { Base::Console().error("File already exists '%s'\n", info.path().toStdString().c_str()); throw MaterialExists(); } } if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream stream(&file); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) stream.setCodec("UTF-8"); #endif stream.setGenerateByteOrderMark(true); // Write the contents material->setName(info.fileName().remove(QStringLiteral(".FCMat"), Qt::CaseInsensitive)); material->setLibrary(getptr()); material->setDirectory(getRelativePath(path)); material->save(stream, overwrite, saveAsCopy, saveInherited); } return addMaterial(material, path); } bool MaterialLibraryLocal::fileExists(const QString& path) const { QString filePath = getLocalPath(path); QFileInfo info(filePath); return info.exists(); } std::shared_ptr MaterialLibraryLocal::addMaterial(const std::shared_ptr& material, const QString& path) { QString filePath = getRelativePath(path); QFileInfo info(filePath); std::shared_ptr newMaterial = std::make_shared(*material); newMaterial->setLibrary(getptr()); newMaterial->setDirectory(getLibraryPath(filePath, info.fileName())); newMaterial->setFilename(info.fileName()); (*_materialPathMap)[filePath] = newMaterial; return newMaterial; } std::shared_ptr MaterialLibraryLocal::getMaterialByPath(const QString& path) const { QString filePath = getRelativePath(path); auto search = _materialPathMap->find(filePath); if (search != _materialPathMap->end()) { return search->second; } throw MaterialNotFound(); } QString MaterialLibraryLocal::getUUIDFromPath(const QString& path) const { QString filePath = getRelativePath(path); auto search = _materialPathMap->find(filePath); if (search != _materialPathMap->end()) { return search->second->getUUID(); } throw MaterialNotFound(); }