From f950a0c086e3f43d67b3e48066931077c424be69 Mon Sep 17 00:00:00 2001 From: David Carter Date: Sat, 6 Apr 2024 15:41:07 -0400 Subject: [PATCH] Material: Compatibility with older FCMat files Provides compatibility loading older files outside the context of a library. Older material files were loaded by specifying a path. The new material system used the path to associated the material with a library, which may not be appropriate for legacy files. This change allows the use of materials outside of a library. Additionally, legacy files often have name/value pairs not part of the standard list of properties. Since these were unable to be mapped to a model property they were ignored. Materials now maintain a legacy map to hold properties not associated with a property model. These properties are considered transient and will not be saved. It is not intended for this feature to be used as a generic container for properties not mapped to an appropriate model. Fixes #13302 --- src/Mod/Material/App/MaterialConfigLoader.cpp | 17 +++++++++ src/Mod/Material/App/MaterialConfigLoader.h | 10 +++++ src/Mod/Material/App/MaterialManager.cpp | 11 ++++++ src/Mod/Material/App/MaterialPy.xml | 11 ++++++ src/Mod/Material/App/MaterialPyImpl.cpp | 37 +++++++++++++++++++ src/Mod/Material/App/Materials.cpp | 27 ++++++++++++++ src/Mod/Material/App/Materials.h | 17 +++++++++ .../Material/materialtests/TestMaterials.py | 5 +++ 8 files changed, 135 insertions(+) diff --git a/src/Mod/Material/App/MaterialConfigLoader.cpp b/src/Mod/Material/App/MaterialConfigLoader.cpp index b16b14daa8..131f1ada3d 100644 --- a/src/Mod/Material/App/MaterialConfigLoader.cpp +++ b/src/Mod/Material/App/MaterialConfigLoader.cpp @@ -1017,6 +1017,22 @@ void MaterialConfigLoader::addMechanical(const QMap& fcmat, setPhysicalValue(finalModel, "Stiffness", stiffness); } +void MaterialConfigLoader::addLegacy(const QMap& fcmat, + const std::shared_ptr& finalModel) +{ + for (auto const& legacy : fcmat.keys()) { + auto name = legacy; + int last = name.lastIndexOf(QLatin1String("/")); + if (last > 0) { + name = name.mid(last + 1); + } + + if (!finalModel->hasNonLegacyProperty(name)) { + setLegacyValue(finalModel, name.toStdString(), fcmat[legacy]); + } + } +} + std::shared_ptr MaterialConfigLoader::getMaterialFromPath(const std::shared_ptr& library, const QString& path) @@ -1081,6 +1097,7 @@ MaterialConfigLoader::getMaterialFromPath(const std::shared_ptr addRendering(fcmat, finalModel); addVectorRendering(fcmat, finalModel); addRenderWB(fcmat, finalModel); + addLegacy(fcmat, finalModel); return finalModel; } diff --git a/src/Mod/Material/App/MaterialConfigLoader.h b/src/Mod/Material/App/MaterialConfigLoader.h index f7b3d6b574..8e50077a72 100644 --- a/src/Mod/Material/App/MaterialConfigLoader.h +++ b/src/Mod/Material/App/MaterialConfigLoader.h @@ -85,6 +85,14 @@ private: finalModel->setAppearanceValue(QString::fromStdString(name), value); } } + static void setLegacyValue(const std::shared_ptr& finalModel, + const std::string& name, + const QString& value) + { + if (!value.isEmpty()) { + finalModel->setLegacyValue(QString::fromStdString(name), value); + } + } static bool isTexture(const QString& value) { @@ -147,6 +155,8 @@ private: const std::shared_ptr& finalModel); static void addRenderWB(QMap& fcmat, const std::shared_ptr& finalModel); + static void addLegacy(const QMap& fcmat, + const std::shared_ptr& finalModel); }; } // namespace Materials diff --git a/src/Mod/Material/App/MaterialManager.cpp b/src/Mod/Material/App/MaterialManager.cpp index b16299c569..b4986bc38d 100644 --- a/src/Mod/Material/App/MaterialManager.cpp +++ b/src/Mod/Material/App/MaterialManager.cpp @@ -196,6 +196,17 @@ std::shared_ptr MaterialManager::getMaterialByPath(const QString& path } } + // Older workbenches may try files outside the context of a library + { + QMutexLocker locker(&_mutex); + + if (MaterialConfigLoader::isConfigStyle(path)) { + auto material = MaterialConfigLoader::getMaterialFromPath(nullptr, path); + + return material; + } + } + throw MaterialNotFound(); } diff --git a/src/Mod/Material/App/MaterialPy.xml b/src/Mod/Material/App/MaterialPy.xml index e16a95f804..8d3d7f14d5 100644 --- a/src/Mod/Material/App/MaterialPy.xml +++ b/src/Mod/Material/App/MaterialPy.xml @@ -141,6 +141,11 @@ Check if the material implements the appearance property with the given name + + + Returns true of there are legacy properties + + deprecated -- Dictionary of all material properties. @@ -159,6 +164,12 @@ + + + deprecated -- Dictionary of material legacy properties. + + + Get the value associated with the property diff --git a/src/Mod/Material/App/MaterialPyImpl.cpp b/src/Mod/Material/App/MaterialPyImpl.cpp index dacb328875..bf959bb72d 100644 --- a/src/Mod/Material/App/MaterialPyImpl.cpp +++ b/src/Mod/Material/App/MaterialPyImpl.cpp @@ -280,6 +280,16 @@ PyObject* MaterialPy::hasAppearanceProperty(PyObject* args) return PyBool_FromLong(hasProperty ? 1 : 0); } +PyObject* MaterialPy::hasLegacyProperties(PyObject* args) +{ + if (!PyArg_ParseTuple(args, "")) { + return nullptr; + } + + bool hasProperty = getMaterialPtr()->hasLegacyProperties(); + return PyBool_FromLong(hasProperty ? 1 : 0); +} + Py::Dict MaterialPy::getProperties() const { Py::Dict dict; @@ -319,6 +329,16 @@ Py::Dict MaterialPy::getProperties() const } } + auto legacy = getMaterialPtr()->getLegacyProperties(); + for (auto& it : legacy) { + auto key = it.first; + auto value = it.second; + + if (!value.isEmpty()) { + dict.setItem(Py::String(key.toStdString()), Py::String(value.toStdString())); + } + } + return dict; } @@ -358,6 +378,23 @@ Py::Dict MaterialPy::getAppearanceProperties() const return dict; } +Py::Dict MaterialPy::getLegacyProperties() const +{ + Py::Dict dict; + + auto legacy = getMaterialPtr()->getLegacyProperties(); + for (auto& it : legacy) { + auto key = it.first; + auto value = it.second; + + if (!value.isEmpty()) { + dict.setItem(Py::String(key.toStdString()), Py::String(value.toStdString())); + } + } + + return dict; +} + static Py::List getList(const QVariant& value) { auto listValue = value.value>(); diff --git a/src/Mod/Material/App/Materials.cpp b/src/Mod/Material/App/Materials.cpp index 7ca9f94f97..2545ca5825 100644 --- a/src/Mod/Material/App/Materials.cpp +++ b/src/Mod/Material/App/Materials.cpp @@ -499,6 +499,9 @@ Material::Material(const Material& other) MaterialProperty prop(it.second); _appearance[it.first] = std::make_shared(prop); } + for (auto& it : other._legacy) { + _legacy[it.first] = it.second; + } } QString Material::getAuthorAndLicense() const @@ -890,6 +893,13 @@ void Material::setAppearanceValue(const QString& name, } } +void Material::setLegacyValue(const QString& name, const QString& value) +{ + setEditStateAlter(); + + _legacy[name] = value; +} + std::shared_ptr Material::getPhysicalProperty(const QString& name) { try { @@ -1047,6 +1057,19 @@ bool Material::hasAppearanceProperty(const QString& name) const return true; } +bool Material::hasNonLegacyProperty(const QString& name) const +{ + if (hasPhysicalProperty(name) || hasAppearanceProperty(name)) { + return true; + } + return false; +} + +bool Material::hasLegacyProperties() const +{ + return !_legacy.empty(); +} + bool Material::isInherited(const QString& uuid) const { if (_physicalUuids.contains(uuid)) { @@ -1464,6 +1487,10 @@ Material& Material::operator=(const Material& other) MaterialProperty prop(it.second); _appearance[it.first] = std::make_shared(prop); } + _legacy.clear(); + for (auto& it : other._legacy) { + _legacy[it.first] = it.second; + } return *this; } diff --git a/src/Mod/Material/App/Materials.h b/src/Mod/Material/App/Materials.h index bdc10dca23..d0a7c6c777 100644 --- a/src/Mod/Material/App/Materials.h +++ b/src/Mod/Material/App/Materials.h @@ -299,6 +299,15 @@ public: void setAppearanceValue(const QString& name, const std::shared_ptr& value); void setAppearanceValue(const QString& name, const std::shared_ptr>& value); + /* + * Legacy values are thosed contained in old format files that don't fit in the new + * property format. It should not be used as a catch all for defining a property with + * no model. + * + * These values are transient and will not be saved. + */ + void setLegacyValue(const QString& name, const QString& value); + std::shared_ptr getPhysicalProperty(const QString& name); std::shared_ptr getPhysicalProperty(const QString& name) const; std::shared_ptr getAppearanceProperty(const QString& name); @@ -313,6 +322,9 @@ public: QString getAppearanceValueString(const QString& name) const; bool hasPhysicalProperty(const QString& name) const; bool hasAppearanceProperty(const QString& name) const; + bool hasNonLegacyProperty(const QString& name) const; + bool hasLegacyProperty(const QString& name) const; + bool hasLegacyProperties() const; // Test if the model is defined, and if values are provided for all properties bool hasModel(const QString& uuid) const; @@ -334,6 +346,10 @@ public: { return _appearance; } + std::map& getLegacyProperties() + { + return _legacy; + } QString getModelByName(const QString& name) const; @@ -438,6 +454,7 @@ private: QSet _allUuids; // Includes inherited models std::map> _physical; std::map> _appearance; + std::map _legacy; bool _dereferenced; bool _oldFormat; ModelEdit _editState; diff --git a/src/Mod/Material/materialtests/TestMaterials.py b/src/Mod/Material/materialtests/TestMaterials.py index 31afa42045..c77248ee04 100644 --- a/src/Mod/Material/materialtests/TestMaterials.py +++ b/src/Mod/Material/materialtests/TestMaterials.py @@ -71,6 +71,8 @@ class MaterialTestCases(unittest.TestCase): self.assertFalse(steel.isPhysicalModelComplete(self.uuids.LinearElastic)) self.assertTrue(steel.isAppearanceModelComplete(self.uuids.BasicRendering)) + self.assertFalse(steel.hasLegacyProperties()) + self.assertTrue(steel.hasPhysicalProperty("Density")) self.assertTrue(steel.hasPhysicalProperty("BulkModulus")) self.assertTrue(steel.hasPhysicalProperty("PoissonRatio")) @@ -118,6 +120,9 @@ class MaterialTestCases(unittest.TestCase): self.assertIn("SpecularColor", properties) self.assertIn("Transparency", properties) + properties = steel.LegacyProperties + self.assertEqual(len(properties), 0) + properties = steel.Properties self.assertIn("Density", properties) self.assertNotIn("BulkModulus", properties)