Materials: External Modules Part 1

Refactored code to support local and external material sources

This is the first PR in a series to support external modules. External
modules allow materials to be stored in external data sources such as
databases or web services. No new functionality is introduced in this
PR, rather it is a refactoring of code that will allow for changes to
be introduced in future PRs. Minor performance improvements have also
been made in the model and material managers.

The Python API has been enhanced for many data types to allow for
modification within Python.
This commit is contained in:
David Carter
2025-03-07 10:13:56 -05:00
committed by Chris Hennes
parent 382720b82e
commit 592406a328
80 changed files with 4372 additions and 1396 deletions

View File

@@ -41,18 +41,121 @@ using namespace Materials;
/* TRANSLATOR Material::Materials */
TYPESYSTEM_SOURCE(Materials::MaterialLibrary, Materials::LibraryBase)
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,
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>>>())
: Library(libraryName, dir, icon, readOnly)
, _local(false)
{}
void MaterialLibrary::createFolder(const QString& path)
bool MaterialLibrary::isLocal() const
{
return _local;
}
void MaterialLibrary::setLocal(bool local)
{
_local = local;
}
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>>
MaterialLibrary::getMaterialTree(const std::shared_ptr<Materials::MaterialFilter>& filter,
const Materials::MaterialFilterOptions& options) const
{
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>> materialTree =
std::make_shared<std::map<QString, std::shared_ptr<MaterialTreeNode>>>();
auto materials = MaterialManager::getManager().libraryMaterials(getName(), filter, options);
for (auto& it : *materials) {
auto uuid = std::get<0>(it);
auto path = std::get<1>(it);
auto filename = std::get<2>(it);
QStringList list = path.split(QStringLiteral("/"));
// Start at the root
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>> node =
materialTree;
for (auto& itp : list) {
if (!itp.isEmpty()) {
// 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();
}
}
}
std::shared_ptr<MaterialTreeNode> child = std::make_shared<MaterialTreeNode>();
child->setUUID(uuid);
(*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<const Materials::MaterialLibraryLocal*>(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->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;
}
/* 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<std::map<QString, std::shared_ptr<Material>>>())
{
setLocal(true);
}
void MaterialLibraryLocal::createFolder(const QString& path)
{
QString filePath = getLocalPath(path);
@@ -65,8 +168,42 @@ void MaterialLibrary::createFolder(const QString& path)
}
}
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 MaterialLibrary::deleteDir(MaterialManager& manager, const QString& path)
void MaterialLibraryLocal::deleteDir(MaterialManager& manager, const QString& path)
{
// Remove the children first
QDirIterator it(path, QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
@@ -105,7 +242,7 @@ void MaterialLibrary::deleteDir(MaterialManager& manager, const QString& path)
}
// This accepts the filesystem path as returned from getLocalPath
void MaterialLibrary::deleteFile(MaterialManager& manager, const QString& path)
void MaterialLibraryLocal::deleteFile(MaterialManager& manager, const QString& path)
{
if (QFile::remove(path)) {
// Remove from the map
@@ -125,25 +262,7 @@ void MaterialLibrary::deleteFile(MaterialManager& manager, const QString& path)
}
}
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)
void MaterialLibraryLocal::updatePaths(const QString& oldPath, const QString& newPath)
{
// Update the path map
QString op = getRelativePath(oldPath);
@@ -162,27 +281,12 @@ void MaterialLibrary::updatePaths(const QString& oldPath, const QString& newPath
_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)
std::shared_ptr<Material>
MaterialLibraryLocal::saveMaterial(const std::shared_ptr<Material>& material,
const QString& path,
bool overwrite,
bool saveAsCopy,
bool saveInherited)
{
QString filePath = getLocalPath(path);
QFile file(filePath);
@@ -220,7 +324,7 @@ std::shared_ptr<Material> MaterialLibrary::saveMaterial(const std::shared_ptr<Ma
return addMaterial(material, path);
}
bool MaterialLibrary::fileExists(const QString& path) const
bool MaterialLibraryLocal::fileExists(const QString& path) const
{
QString filePath = getLocalPath(path);
QFileInfo info(filePath);
@@ -228,137 +332,41 @@ bool MaterialLibrary::fileExists(const QString& path) const
return info.exists();
}
std::shared_ptr<Material> MaterialLibrary::addMaterial(const std::shared_ptr<Material>& material,
const QString& path)
std::shared_ptr<Material>
MaterialLibraryLocal::addMaterial(const std::shared_ptr<Material>& material, const QString& path)
{
QString filePath = getRelativePath(path);
QFileInfo info(filePath);
std::shared_ptr<Material> newMaterial = std::make_shared<Material>(*material);
newMaterial->setLibrary(getptr());
newMaterial->setDirectory(filePath);
newMaterial->setDirectory(getLibraryPath(filePath, info.fileName()));
newMaterial->setFilename(info.fileName());
(*_materialPathMap)[filePath] = newMaterial;
return newMaterial;
}
std::shared_ptr<Material> MaterialLibrary::getMaterialByPath(const QString& path) const
std::shared_ptr<Material> MaterialLibraryLocal::getMaterialByPath(const QString& path) const
{
QString filePath = getRelativePath(path);
try {
auto material = _materialPathMap->at(filePath);
return material;
}
catch (std::out_of_range&) {
throw MaterialNotFound();
auto search = _materialPathMap->find(filePath);
if (search != _materialPathMap->end()) {
return search->second;
}
throw MaterialNotFound();
}
QString MaterialLibrary::getUUIDFromPath(const QString& path) const
QString MaterialLibraryLocal::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();
auto search = _materialPathMap->find(filePath);
if (search != _materialPathMap->end()) {
return search->second->getUUID();
}
throw MaterialNotFound();
}
bool MaterialLibrary::materialInTree(const std::shared_ptr<Material>& material,
const std::shared_ptr<Materials::MaterialFilter>& filter,
const Materials::MaterialFilterOptions& options) const
{
if (!filter) {
// If there's no filter we always include
return true;
}
// filter out old format files
if (material->isOldFormat() && !options.includeLegacy()) {
return false;
}
// filter based on models
return filter->modelIncluded(material);
}
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>>
MaterialLibrary::getMaterialTree(const std::shared_ptr<Materials::MaterialFilter>& filter,
const Materials::MaterialFilterOptions& options) 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, options)) {
QStringList list = filename.split(QStringLiteral("/"));
// Start at the root
std::shared_ptr<std::map<QString, std::shared_ptr<MaterialTreeNode>>> node =
materialTree;
for (auto& itp : list) {
if (itp.endsWith(QStringLiteral(".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 || options.includeEmptyFolders()) {
auto folderList = MaterialLoader::getMaterialFolders(*this);
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->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)
{}