Material: Material handling enhancements

Rework of the material handling system.

This first part concntrates on a rework of the material cards.
Rather than use a fixed list of possible properties, properties can
be defined separately in their own files and mixed to provide a
complete list of possible properties. Properties can be inherited.

The cards then provide values for the properties. These can also
be inherited allowing for small changes in cards as required.

The new property definitions are more extensive than previously.
2 and 3 dimensional arrays of properties can be defined. Values
are obtained by calling an API instead of reading from a dictionary.

For compatibility, a Python dictionary of values can be obtained
similar to how it was done previously, but this is considered a
deprecated API and won't support the newer advanced features.

The editor is completely reworked. It will be able to edit older format
material cards, but can only save them in the new format.

For testing during the development phase, a system preference can
specifiy wether the old or new material editors are to be used. This
option will be removed before release.
This commit is contained in:
David Carter
2023-09-14 22:52:48 -04:00
parent 6b0c054023
commit 902af79514
397 changed files with 19222 additions and 4312 deletions

View File

@@ -0,0 +1,445 @@
/***************************************************************************
* Copyright (c) 2023 David Carter <dcarter@david.carter.ca> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <QString>
#endif
#include <App/Application.h>
#include <Base/Interpreter.h>
#include <QDirIterator>
#include <QFileInfo>
#include "MaterialConfigLoader.h"
#include "MaterialLoader.h"
#include "Model.h"
#include "ModelManager.h"
using namespace Materials;
MaterialEntry::MaterialEntry()
{}
MaterialEntry::MaterialEntry(const MaterialLibrary& library,
const QString& modelName,
const QString& dir,
const QString& modelUuid)
: _library(library)
, _name(modelName)
, _directory(dir)
, _uuid(modelUuid)
{}
MaterialYamlEntry::MaterialYamlEntry(const MaterialLibrary& library,
const QString& modelName,
const QString& dir,
const QString& modelUuid,
const YAML::Node& modelData)
: MaterialEntry(library, modelName, dir, modelUuid)
, _model(modelData)
{}
// MaterialYamlEntry::~MaterialYamlEntry()
// {}
QString MaterialYamlEntry::yamlValue(const YAML::Node& node,
const std::string& key,
const std::string& defaultValue)
{
if (node[key]) {
return QString::fromStdString(node[key].as<std::string>());
}
return QString::fromStdString(defaultValue);
}
void MaterialYamlEntry::addToTree(std::map<QString, Material*>* materialMap)
{
std::set<QString> exclude;
exclude.insert(QString::fromStdString("General"));
exclude.insert(QString::fromStdString("Inherits"));
auto yamlModel = getModel();
auto library = getLibrary();
auto name = getName();
auto directory = getDirectory();
QString uuid = getUUID();
QString authorAndLicense = yamlValue(yamlModel["General"], "AuthorAndLicense", "");
QString description = yamlValue(yamlModel["General"], "Description", "");
Material* finalModel = new Material(library, directory, uuid, name);
finalModel->setAuthorAndLicense(authorAndLicense);
finalModel->setDescription(description);
// Add inheritance list
if (yamlModel["Inherits"]) {
auto inherits = yamlModel["Inherits"];
for (auto it = inherits.begin(); it != inherits.end(); it++) {
std::string nodeName = it->second["UUID"].as<std::string>();
finalModel->setParentUUID(
QString::fromStdString(nodeName));// Should only be one. Need to check
}
}
// Add material models
if (yamlModel["Models"]) {
auto models = yamlModel["Models"];
for (auto it = models.begin(); it != models.end(); it++) {
std::string modelName = (it->first).as<std::string>();
// Add the model uuid
auto modelNode = models[modelName];
std::string modelUUID = modelNode["UUID"].as<std::string>();
finalModel->addPhysical(QString::fromStdString(modelUUID));
// Add the property values
auto properties = yamlModel["Models"][modelName];
for (auto itp = properties.begin(); itp != properties.end(); itp++) {
std::string propertyName = (itp->first).as<std::string>();
std::string propertyValue = (itp->second).as<std::string>();
if (finalModel->hasPhysicalProperty(QString::fromStdString(propertyName))) {
finalModel->setPhysicalValue(QString::fromStdString(propertyName),
QString::fromStdString(propertyValue));
}
else if (propertyName != "UUID") {
Base::Console().Log("\tProperty '%s' is not described by any model. Ignored\n",
propertyName.c_str());
}
}
}
}
// Add appearance models
if (yamlModel["AppearanceModels"]) {
auto models = yamlModel["AppearanceModels"];
for (auto it = models.begin(); it != models.end(); it++) {
std::string modelName = (it->first).as<std::string>();
// Add the model uuid
auto modelNode = models[modelName];
std::string modelUUID = modelNode["UUID"].as<std::string>();
finalModel->addAppearance(QString::fromStdString(modelUUID));
// Add the property values
auto properties = yamlModel["AppearanceModels"][modelName];
for (auto itp = properties.begin(); itp != properties.end(); itp++) {
std::string propertyName = (itp->first).as<std::string>();
std::string propertyValue = (itp->second).as<std::string>();
if (finalModel->hasAppearanceProperty(QString::fromStdString(propertyName))) {
finalModel->setAppearanceValue(QString::fromStdString(propertyName),
QString::fromStdString(propertyValue));
}
else if (propertyName != "UUID") {
Base::Console().Log("\tProperty '%s' is not described by any model. Ignored\n",
propertyName.c_str());
}
}
}
}
QString path = QDir(directory).absolutePath();
// Base::Console().Log("\tPath '%s'\n", path.toStdString().c_str());
(*materialMap)[uuid] = library.addMaterial(*finalModel, path);
}
std::map<QString, MaterialEntry*>* MaterialLoader::_materialEntryMap = nullptr;
MaterialLoader::MaterialLoader(std::map<QString, Material*>* materialMap,
std::list<MaterialLibrary*>* libraryList)
: _materialMap(materialMap)
, _libraryList(libraryList)
{
loadLibraries();
}
/*
* Destroys the object and frees any allocated resources
*/
MaterialLoader::~MaterialLoader()
{}
void MaterialLoader::addLibrary(MaterialLibrary* model)
{
_libraryList->push_back(model);
}
MaterialEntry* MaterialLoader::getMaterialFromPath(MaterialLibrary& library,
const QString& path) const
{
MaterialEntry* model = nullptr;
// Used for debugging
std::string uuid;
std::string pathName = path.toStdString();
if (MaterialConfigLoader::isConfigStyle(path)) {
Base::Console().Log("Old format .FCMat file: '%s'\n", pathName.c_str());
Material* material = MaterialConfigLoader::getMaterialFromPath(library, path);
if (material) {
(*_materialMap)[material->getUUID()] = library.addMaterial(*material, path);
}
// Return the nullptr as there are no intermediate steps to take, such
// as checking inheritance
return model;
}
YAML::Node yamlroot;
try {
yamlroot = YAML::LoadFile(pathName);
const std::string uuid = yamlroot["General"]["UUID"].as<std::string>();
// Always get the name from the filename
// QString name = QString::fromStdString(yamlroot["General"]["Name"].as<std::string>());
QFileInfo filepath(path);
QString name =
filepath.fileName().remove(QString::fromStdString(".FCMat"), Qt::CaseInsensitive);
model = new MaterialYamlEntry(library, name, path, QString::fromStdString(uuid), yamlroot);
// showYaml(yamlroot);
}
catch (YAML::Exception const& e) {
Base::Console().Error("YAML parsing error: '%s'\n", pathName.c_str());
Base::Console().Error("\t'%s'\n", e.what());
showYaml(yamlroot);
}
return model;
}
void MaterialLoader::showYaml(const YAML::Node& yaml)
{
std::stringstream out;
out << yaml;
std::string logData = out.str();
Base::Console().Log("%s\n", logData.c_str());
}
void MaterialLoader::dereference(Material* material)
{
// Avoid recursion
if (material->getDereferenced()) {
return;
}
// Base::Console().Log("Dereferencing material '%s'.\n",
// material->getName().toStdString().c_str());
auto parentUUID = material->getParentUUID();
if (parentUUID.size() > 0) {
Material* parent;
try {
parent = (*_materialMap)[parentUUID];
}
catch (std::out_of_range& e) {
Base::Console().Log(
"Unable to apply inheritance for material '%s', parent '%s' not found.\n",
material->getName().toStdString().c_str(),
parentUUID.toStdString().c_str());
}
// Ensure the parent has been dereferenced
dereference(parent);
// Add physical models
auto modelVector = parent->getPhysicalModels();
for (auto model = modelVector->begin(); model != modelVector->end(); model++) {
if (!material->hasPhysicalModel(*model)) {
material->addPhysical(*model);
}
}
// Add appearance models
modelVector = parent->getAppearanceModels();
for (auto model = modelVector->begin(); model != modelVector->end(); model++) {
if (!material->hasAppearanceModel(*model)) {
material->addAppearance(*model);
}
}
// Add values
auto properties = parent->getPhysicalProperties();
for (auto itp = properties.begin(); itp != properties.end(); itp++) {
auto name = itp->first;
auto property = static_cast<const MaterialProperty>(itp->second);
if (material->getPhysicalProperty(name).isNull()) {
material->getPhysicalProperty(name).setValue(property.getValue());
}
}
properties = parent->getAppearanceProperties();
for (auto itp = properties.begin(); itp != properties.end(); itp++) {
auto name = itp->first;
auto property = static_cast<const MaterialProperty>(itp->second);
if (material->getAppearanceProperty(name).isNull()) {
material->getAppearanceProperty(name).setValue(property.getValue());
}
}
}
material->markDereferenced();
}
void MaterialLoader::loadLibrary(MaterialLibrary& library)
{
if (_materialEntryMap == nullptr) {
_materialEntryMap = new std::map<QString, MaterialEntry*>();
}
QDirIterator it(library.getDirectory(), QDirIterator::Subdirectories);
while (it.hasNext()) {
auto pathname = it.next();
QFileInfo file(pathname);
if (file.isFile()) {
if (file.suffix().toStdString() == "FCMat") {
QString libraryName = file.baseName();
auto model = getMaterialFromPath(library, file.canonicalFilePath());
if (model) {
(*_materialEntryMap)[model->getUUID()] = model;
}
}
}
}
for (auto it = _materialEntryMap->begin(); it != _materialEntryMap->end(); it++) {
it->second->addToTree(_materialMap);
}
}
void MaterialLoader::loadLibraries(void)
{
std::list<MaterialLibrary*>* _libraryList = getMaterialLibraries();
if (_libraryList) {
for (auto it = _libraryList->begin(); it != _libraryList->end(); it++) {
loadLibrary(**it);
}
}
for (auto it = _materialMap->begin(); it != _materialMap->end(); it++) {
dereference(it->second);
}
}
std::list<MaterialLibrary*>* MaterialLoader::getMaterialLibraries()
{
auto param = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Material/Resources");
bool useBuiltInMaterials = param->GetBool("UseBuiltInMaterials", true);
bool useMatFromModules = param->GetBool("UseMaterialsFromWorkbenches", true);
bool useMatFromConfigDir = param->GetBool("UseMaterialsFromConfigDir", true);
bool useMatFromCustomDir = param->GetBool("UseMaterialsFromCustomDir", true);
if (useBuiltInMaterials) {
QString resourceDir = QString::fromStdString(App::Application::getResourceDir()
+ "/Mod/Material/Resources/Materials");
auto libData = new MaterialLibrary(QString::fromStdString("System"),
resourceDir,
QString::fromStdString(":/icons/freecad.svg"),
true);
_libraryList->push_back(libData);
}
if (useMatFromModules) {
auto moduleParam = App::GetApplication().GetParameterGroupByPath(
"User parameter:BaseApp/Preferences/Mod/Material/Resources/Modules");
for (auto& group : moduleParam->GetGroups()) {
// auto module = moduleParam->GetGroup(group->GetGroupName());
auto moduleName = QString::fromStdString(group->GetGroupName());
auto materialDir = QString::fromStdString(group->GetASCII("ModuleDir", ""));
auto materialIcon = QString::fromStdString(group->GetASCII("ModuleIcon", ""));
auto materialReadOnly = group->GetBool("ModuleReadOnly", true);
if (materialDir.length() > 0) {
QDir dir(materialDir);
if (dir.exists()) {
auto libData = new MaterialLibrary(moduleName,
materialDir,
materialIcon,
materialReadOnly);
_libraryList->push_back(libData);
}
}
}
}
if (useMatFromConfigDir) {
QString resourceDir =
QString::fromStdString(App::Application::getUserAppDataDir() + "/Material");
if (!resourceDir.isEmpty()) {
QDir materialDir(resourceDir);
if (materialDir.exists()) {
auto libData =
new MaterialLibrary(QString::fromStdString("User"),
resourceDir,
QString::fromStdString(":/icons/preferences-general.svg"),
false);
_libraryList->push_back(libData);
}
}
}
if (useMatFromCustomDir) {
QString resourceDir = QString::fromStdString(param->GetASCII("CustomMaterialsDir", ""));
if (!resourceDir.isEmpty()) {
QDir materialDir(resourceDir);
if (materialDir.exists()) {
auto libData = new MaterialLibrary(QString::fromStdString("Custom"),
resourceDir,
QString::fromStdString(":/icons/user.svg"),
false);
_libraryList->push_back(libData);
}
}
}
return _libraryList;
}
std::list<QString>* MaterialLoader::getMaterialFolders(const MaterialLibrary& library)
{
std::list<QString>* pathList = new std::list<QString>();
QDirIterator it(library.getDirectory(), QDirIterator::Subdirectories);
while (it.hasNext()) {
auto pathname = it.next();
QFileInfo file(pathname);
if (file.isDir()) {
QString path = QDir(library.getDirectory()).relativeFilePath(file.absoluteFilePath());
if (!path.startsWith(QString::fromStdString("."))) {
pathList->push_back(path);
}
}
}
return pathList;
}