Files
create/src/Mod/Material/App/MaterialLibrary.cpp
2024-01-22 08:53:38 -06:00

363 lines
12 KiB
C++

/***************************************************************************
* Copyright (c) 2023 David Carter <dcarter@david.carter.ca> *
* *
* 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 *
* <https://www.gnu.org/licenses/>. *
* *
**************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <QVector>
#include <QDirIterator>
#include <QFileInfo>
#endif
#include <App/Application.h>
#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, Materials::LibraryBase)
MaterialLibrary::MaterialLibrary(const QString& libraryName,
const QString& dir,
const QString& icon,
bool readOnly)
: LibraryBase(libraryName, dir, icon)
, _readOnly(readOnly)
, _materialPathMap(std::make_unique<std::map<QString, std::shared_ptr<Material>>>())
{}
void MaterialLibrary::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());
}
}
}
// This accepts the filesystem path as returned from getLocalPath
void MaterialLibrary::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<QString> dirList;
QVector<QString> 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 MaterialLibrary::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 = QString::fromStdString("DeleteError: Unable to delete ") + path;
throw DeleteError(error);
}
}
void MaterialLibrary::deleteRecursive(const QString& path)
{
if (isRoot(path)) {
return;
}
QString filePath = getLocalPath(path);
MaterialManager manager;
QFileInfo info(filePath);
if (info.isDir()) {
deleteDir(manager, filePath);
}
else {
deleteFile(manager, filePath);
}
}
void MaterialLibrary::updatePaths(const QString& oldPath, const QString& newPath)
{
// Update the path map
QString op = getRelativePath(oldPath);
QString np = getRelativePath(newPath);
std::unique_ptr<std::map<QString, std::shared_ptr<Material>>> pathMap =
std::make_unique<std::map<QString, std::shared_ptr<Material>>>();
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);
}
void MaterialLibrary::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);
}
std::shared_ptr<Material> MaterialLibrary::saveMaterial(const std::shared_ptr<Material>& 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.baseName());
material->setLibrary(getptr());
material->setDirectory(getRelativePath(path));
material->save(stream, overwrite, saveAsCopy, saveInherited);
}
return addMaterial(material, path);
}
bool MaterialLibrary::fileExists(const QString& path) const
{
QString filePath = getLocalPath(path);
QFileInfo info(filePath);
return info.exists();
}
std::shared_ptr<Material> MaterialLibrary::addMaterial(const std::shared_ptr<Material>& material,
const QString& path)
{
QString filePath = getRelativePath(path);
std::shared_ptr<Material> newMaterial = std::make_shared<Material>(*material);
newMaterial->setLibrary(getptr());
newMaterial->setDirectory(filePath);
(*_materialPathMap)[filePath] = newMaterial;
return newMaterial;
}
std::shared_ptr<Material> MaterialLibrary::getMaterialByPath(const QString& path) const
{
QString filePath = getRelativePath(path);
try {
auto material = _materialPathMap->at(filePath);
return material;
}
catch (std::out_of_range&) {
throw MaterialNotFound();
}
}
QString MaterialLibrary::getUUIDFromPath(const QString& path) const
{
QString filePath = getRelativePath(path);
try {
auto material = _materialPathMap->at(filePath);
return material->getUUID();
}
catch (std::out_of_range&) {
throw MaterialNotFound();
}
}
bool MaterialLibrary::materialInTree(const std::shared_ptr<Material>& material,
const MaterialFilter* filter) const
{
if (!filter) {
// If there's no filter we always include
return true;
}
// filter out old format files
if (material->isOldFormat() && !filter->includeLegacy()) {
return false;
}
// filter based on models
return filter->modelIncluded(material);
}
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>>
MaterialLibrary::getMaterialTree(const MaterialFilter* filter) const
{
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>> materialTree =
std::make_shared<std::map<QString, std::shared_ptr<MaterialTreeNode>>>();
for (auto& it : *_materialPathMap) {
auto filename = it.first;
auto material = it.second;
if (materialInTree(material, filter)) {
QStringList list = filename.split(QString::fromStdString("/"));
// Start at the root
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>> node =
materialTree;
for (auto& itp : list) {
if (itp.endsWith(QString::fromStdString(".FCMat"))) {
std::shared_ptr<MaterialTreeNode> child = std::make_shared<MaterialTreeNode>();
child->setData(material);
(*node)[itp] = child;
}
else {
// Add the folder only if it's not already there
if (node->count(itp) == 0) {
auto mapPtr = std::make_shared<
std::map<QString, std::shared_ptr<MaterialTreeNode>>>();
std::shared_ptr<MaterialTreeNode> child =
std::make_shared<MaterialTreeNode>();
child->setFolder(mapPtr);
(*node)[itp] = child;
node = mapPtr;
}
else {
node = (*node)[itp]->getFolder();
}
}
}
}
}
// Empty folders aren't included in _materialPathMap, so we add them by looking at the file
// system
if (!filter || filter->includeEmptyFolders()) {
auto folderList = MaterialLoader::getMaterialFolders(*this);
for (auto& folder : *folderList) {
QStringList list = folder.split(QString::fromStdString("/"));
// Start at the root
auto node = materialTree;
for (auto& itp : list) {
// Add the folder only if it's not already there
if (node->count(itp) == 0) {
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>> mapPtr =
std::make_shared<std::map<QString, std::shared_ptr<MaterialTreeNode>>>();
std::shared_ptr<MaterialTreeNode> child = std::make_shared<MaterialTreeNode>();
child->setFolder(mapPtr);
(*node)[itp] = child;
node = mapPtr;
}
else {
node = (*node)[itp]->getFolder();
}
}
}
}
return materialTree;
}
TYPESYSTEM_SOURCE(Materials::MaterialExternalLibrary, Materials::MaterialLibrary)
MaterialExternalLibrary::MaterialExternalLibrary(const QString& libraryName,
const QString& dir,
const QString& icon,
bool readOnly)
: MaterialLibrary(libraryName, dir, icon, readOnly)
{}