diff --git a/src/Mod/Material/App/MaterialManagerPy.xml b/src/Mod/Material/App/MaterialManagerPy.xml
index 4ad5991e22..f518062bc6 100644
--- a/src/Mod/Material/App/MaterialManagerPy.xml
+++ b/src/Mod/Material/App/MaterialManagerPy.xml
@@ -52,5 +52,15 @@
Save the material in the specified library
+
+
+ Returns a filtered material list
+
+
+
+
+ Refreshes the material tree. Use sparingly as this is an expensive operation.
+
+
diff --git a/src/Mod/Material/App/MaterialManagerPyImpl.cpp b/src/Mod/Material/App/MaterialManagerPyImpl.cpp
index 80f26cdaef..bd31992db6 100644
--- a/src/Mod/Material/App/MaterialManagerPyImpl.cpp
+++ b/src/Mod/Material/App/MaterialManagerPyImpl.cpp
@@ -22,6 +22,8 @@
#include "PreCompiled.h"
#include "Exceptions.h"
+#include "MaterialFilter.h"
+#include "MaterialFilterPy.h"
#include "MaterialManager.h"
#include "MaterialManagerPy.h"
#include "MaterialPy.h"
@@ -256,5 +258,71 @@ PyObject* MaterialManagerPy::save(PyObject* args, PyObject* kwds)
PyObject_IsTrue(saveInherited));
material->getMaterialPtr()->setUUID(sharedMaterial->getUUID()); // Make sure they match
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+void addMaterials(Py::List& list,
+ const std::shared_ptr>>& tree)
+{
+ for (auto& node : *tree) {
+ if (node.second->getType() == MaterialTreeNode::DataNode) {
+ auto material = node.second->getData();
+ PyObject* materialPy = new MaterialPy(new Material(*material));
+ list.append(Py::Object(materialPy, true));
+ }
+ else {
+ addMaterials(list, node.second->getFolder());
+ }
+ }
+}
+
+PyObject* MaterialManagerPy::filterMaterials(PyObject* args, PyObject* kwds)
+{
+ PyObject* filterPy {};
+ PyObject* includeLegacy = Py_False;
+ static char* kwds_save[] = {"filter",
+ "includeLegacy",
+ nullptr};
+ if (!PyArg_ParseTupleAndKeywords(args,
+ kwds,
+ // "O|O!",
+ "O!|O!",
+ kwds_save,
+ &MaterialFilterPy::Type,
+ &filterPy,
+ &PyBool_Type,
+ &includeLegacy)) {
+ return nullptr;
+ }
+
+ MaterialFilterOptions options;
+ options.setIncludeFavorites(false);
+ options.setIncludeRecent(false);
+ options.setIncludeEmptyFolders(false);
+ options.setIncludeEmptyLibraries(false);
+ options.setIncludeLegacy(PyObject_IsTrue(includeLegacy));
+
+ auto filter = std::make_shared(*(static_cast(filterPy)->getMaterialFilterPtr()));
+
+ auto libraries = getMaterialManagerPtr()->getMaterialLibraries();
+ Py::List list;
+
+ for (auto lib : *libraries) {
+ auto tree = getMaterialManagerPtr()->getMaterialTree(lib, filter, options);
+ if (tree->size() > 0) {
+ addMaterials(list, tree);
+ }
+ }
+
+ Py_INCREF(*list);
+ return *list;
+}
+
+PyObject* MaterialManagerPy::refresh(PyObject* /*args*/)
+{
+ getMaterialManagerPtr()->refresh();
+
+ Py_INCREF(Py_None);
return Py_None;
}
diff --git a/src/Mod/Material/CMakeLists.txt b/src/Mod/Material/CMakeLists.txt
index fc6113bd21..4464472aea 100644
--- a/src/Mod/Material/CMakeLists.txt
+++ b/src/Mod/Material/CMakeLists.txt
@@ -286,6 +286,7 @@ set(MaterialTest_Files
materialtests/TestModels.py
materialtests/TestMaterials.py
materialtests/TestMaterialCreation.py
+ materialtests/TestMaterialFilter.py
)
ADD_CUSTOM_TARGET(MaterialTest ALL
@@ -297,6 +298,23 @@ fc_target_copy_resource(MaterialTest
${CMAKE_BINARY_DIR}/Mod/Material
${MaterialTest_Files})
+set(MaterialPythonTestData_Files
+ materialtests/Materials/TestAcrylicLegacy.FCMat
+ materialtests/Materials/TestAluminumAppearance.FCMat
+ materialtests/Materials/TestAluminumMixed.FCMat
+ materialtests/Materials/TestAluminumPhysical.FCMat
+ materialtests/Materials/TestBrassAppearance.FCMat
+)
+
+ADD_CUSTOM_TARGET(MaterialPythonTestData ALL
+ SOURCES ${MaterialPythonTestData_Files}
+)
+
+fc_target_copy_resource(MaterialPythonTestData
+ ${CMAKE_CURRENT_SOURCE_DIR}
+ ${CMAKE_BINARY_DIR}/Mod/Material
+ ${MaterialPythonTestData_Files})
+
ADD_CUSTOM_TARGET(MaterialScripts ALL
SOURCES ${MaterialScripts_Files} ${Material_Ui_Files} ${Material_QRC_SRCS}
)
@@ -382,6 +400,11 @@ INSTALL(
DESTINATION Mod/Material/materialtests
)
+INSTALL(
+ FILES ${MaterialPythonTestData_Files}
+ DESTINATION Mod/Material/materialtests/Materials
+)
+
foreach(file ${MaterialLib_Files} ${FluidMaterial_Files} ${AppearanceLib_Files} ${PatternLib_Files} ${MaterialTestLib_Files} ${MaterialModel_Files})
get_filename_component(filepath ${file} DIRECTORY)
INSTALL(
diff --git a/src/Mod/Material/TestMaterialsApp.py b/src/Mod/Material/TestMaterialsApp.py
index b368b8c4a7..effe1182c7 100644
--- a/src/Mod/Material/TestMaterialsApp.py
+++ b/src/Mod/Material/TestMaterialsApp.py
@@ -29,3 +29,4 @@ import Materials
from materialtests.TestModels import ModelTestCases
from materialtests.TestMaterials import MaterialTestCases
from materialtests.TestMaterialCreation import MaterialCreationTestCases
+from materialtests.TestMaterialFilter import MaterialFilterTestCases
diff --git a/src/Mod/Material/materialtests/Materials/TestAcrylicLegacy.FCMat b/src/Mod/Material/materialtests/Materials/TestAcrylicLegacy.FCMat
new file mode 100644
index 0000000000..a25c1f3e04
--- /dev/null
+++ b/src/Mod/Material/materialtests/Materials/TestAcrylicLegacy.FCMat
@@ -0,0 +1,13 @@
+; Acrylic
+; Automatically generated by the Rocket Workbench
+; information about the content of such cards can be found on the wiki:
+; https://www.freecadweb.org/wiki/Material
+
+[General]
+Name = TestAcrylicLegacy
+Description = Acrylic
+KindOfMaterial = Solid
+
+[Mechanical]
+Density = 1190.0 kg/m^3
+Hardness = 10
diff --git a/src/Mod/Material/materialtests/Materials/TestAluminumAppearance.FCMat b/src/Mod/Material/materialtests/Materials/TestAluminumAppearance.FCMat
new file mode 100644
index 0000000000..b374cce1e5
--- /dev/null
+++ b/src/Mod/Material/materialtests/Materials/TestAluminumAppearance.FCMat
@@ -0,0 +1,17 @@
+---
+# File created by ConvertFCMat.py
+General:
+ UUID: "3c6d0407-66b3-48ea-a2e8-ee843edf0311"
+ Author: "David Carter"
+ License: "GPL-2.0-or-later"
+ Name: "TestAluminumAppearance"
+ Description: "Defines the Aluminum appearance properties"
+AppearanceModels:
+ BasicRendering:
+ UUID: 'f006c7e4-35b7-43d5-bbf9-c5d572309e6e'
+ AmbientColor: "(0.3000, 0.3000, 0.3000, 1.0)"
+ DiffuseColor: "(0.3000, 0.3000, 0.3000, 1.0)"
+ EmissiveColor: "(0.0000, 0.0000, 0.0000, 1.0)"
+ Shininess: "0.0900"
+ SpecularColor: "(0.3000, 0.3000, 0.3000, 1.0)"
+ Transparency: "0.0"
diff --git a/src/Mod/Material/materialtests/Materials/TestAluminumMixed.FCMat b/src/Mod/Material/materialtests/Materials/TestAluminumMixed.FCMat
new file mode 100644
index 0000000000..400ac77812
--- /dev/null
+++ b/src/Mod/Material/materialtests/Materials/TestAluminumMixed.FCMat
@@ -0,0 +1,33 @@
+---
+# File created by ConvertFCMat.py
+General:
+ UUID: "5f546608-fcbb-40db-98d7-d8e104eb33ce"
+ Author: "M. Münch"
+ License: "LGPL-2.0-or-later"
+ Name: "TestAluminumMixed"
+Inherits:
+ Aluminum:
+ UUID: "3c6d0407-66b3-48ea-a2e8-ee843edf0311"
+Models:
+ Father:
+ UUID: '9cdda8b6-b606-4778-8f13-3934d8668e67'
+ Father: "Metal"
+ MaterialStandard:
+ UUID: '1e2c0088-904a-4537-925f-64064c07d700'
+ KindOfMaterial: "Aluminium"
+ MaterialNumber: "3.3535.26"
+ StandardCode: "DIN 1725"
+ LinearElastic:
+ UUID: '7b561d1d-fb9b-44f6-9da9-56a4f74d7536'
+ Density: "2700 kg/m^3"
+ PoissonRatio: "0.3"
+ ShearModulus: "27000 MPa"
+ UltimateStrain: "5"
+ UltimateTensileStrength: "250 MPa"
+ YieldStrength: "180 MPa"
+ YoungsModulus: "70000 MPa"
+ Thermal:
+ UUID: '9959d007-a970-4ea7-bae4-3eb1b8b883c7'
+ SpecificHeat: "900 J/kg/K"
+ ThermalConductivity: "150 W/m/K"
+ ThermalExpansionCoefficient: "23 µm/m/K"
diff --git a/src/Mod/Material/materialtests/Materials/TestAluminumPhysical.FCMat b/src/Mod/Material/materialtests/Materials/TestAluminumPhysical.FCMat
new file mode 100644
index 0000000000..97312c28e6
--- /dev/null
+++ b/src/Mod/Material/materialtests/Materials/TestAluminumPhysical.FCMat
@@ -0,0 +1,30 @@
+---
+# File created by ConvertFCMat.py
+General:
+ UUID: "a8e60089-550d-4370-8e7e-1734db12a3a9"
+ Author: "M. Münch"
+ License: "LGPL-2.0-or-later"
+ Name: "TestAluminumPhysical"
+Models:
+ Father:
+ UUID: '9cdda8b6-b606-4778-8f13-3934d8668e67'
+ Father: "Metal"
+ MaterialStandard:
+ UUID: '1e2c0088-904a-4537-925f-64064c07d700'
+ KindOfMaterial: "Aluminium"
+ MaterialNumber: "3.3535.26"
+ StandardCode: "DIN 1725"
+ LinearElastic:
+ UUID: '7b561d1d-fb9b-44f6-9da9-56a4f74d7536'
+ Density: "2700 kg/m^3"
+ PoissonRatio: "0.3"
+ ShearModulus: "27000 MPa"
+ # UltimateStrain: "5"
+ UltimateTensileStrength: "250 MPa"
+ YieldStrength: "180 MPa"
+ YoungsModulus: "70000 MPa"
+ Thermal:
+ UUID: '9959d007-a970-4ea7-bae4-3eb1b8b883c7'
+ SpecificHeat: "900 J/kg/K"
+ ThermalConductivity: "150 W/m/K"
+ ThermalExpansionCoefficient: "23 µm/m/K"
diff --git a/src/Mod/Material/materialtests/Materials/TestBrassAppearance.FCMat b/src/Mod/Material/materialtests/Materials/TestBrassAppearance.FCMat
new file mode 100644
index 0000000000..d188a9d853
--- /dev/null
+++ b/src/Mod/Material/materialtests/Materials/TestBrassAppearance.FCMat
@@ -0,0 +1,17 @@
+---
+# File created by ConvertFCMat.py
+General:
+ UUID: "fff3d5c8-98c3-4ee2-8fe5-7e17403c48fcc"
+ Author: "David Carter"
+ License: "GPL-2.0-or-later"
+ Name: "TestBrassAppearance"
+ Description: "Defines the Brass appearance properties"
+AppearanceModels:
+ BasicRendering:
+ UUID: 'f006c7e4-35b7-43d5-bbf9-c5d572309e6e'
+ AmbientColor: "(0.3294, 0.2235, 0.0275, 1.0)"
+ DiffuseColor: "(0.7804, 0.5686, 0.1137, 1.0)"
+ EmissiveColor: "(0.0000, 0.0000, 0.0000, 1.0)"
+ Shininess: "0.2179"
+ SpecularColor: "(0.9922, 0.9412, 0.8078, 1.0)"
+ Transparency: "0.0"
diff --git a/src/Mod/Material/materialtests/TestMaterialFilter.py b/src/Mod/Material/materialtests/TestMaterialFilter.py
new file mode 100644
index 0000000000..a484cb2409
--- /dev/null
+++ b/src/Mod/Material/materialtests/TestMaterialFilter.py
@@ -0,0 +1,192 @@
+# **************************************************************************
+# Copyright (c) 2023 David Carter *
+# *
+# This file is part of the FreeCAD CAx development system. *
+# *
+# This program is free software; you can redistribute it and/or modify *
+# it under the terms of the GNU Lesser General Public License (LGPL) *
+# as published by the Free Software Foundation; either version 2 of *
+# the License, or (at your option) any later version. *
+# for detail see the LICENCE text file. *
+# *
+# 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 Library General Public License for more details. *
+# *
+# You should have received a copy of the GNU Library General Public *
+# License along with FreeCAD; if not, write to the Free Software *
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
+# USA *
+# **************************************************************************
+
+"""
+Test module for FreeCAD material cards and APIs
+"""
+import os
+
+import unittest
+import FreeCAD
+import Materials
+
+parseQuantity = FreeCAD.Units.parseQuantity
+
+UUIDAcrylicLegacy = "" # This can't be known until it is loaded
+UUIDAluminumAppearance = "3c6d0407-66b3-48ea-a2e8-ee843edf0311"
+UUIDAluminumMixed = "5f546608-fcbb-40db-98d7-d8e104eb33ce"
+UUIDAluminumPhysical = "a8e60089-550d-4370-8e7e-1734db12a3a9"
+UUIDBrassAppearance = "fff3d5c8-98c3-4ee2-8fe5-7e17403c48fcc"
+
+
+class MaterialFilterTestCases(unittest.TestCase):
+ """
+ Test class for FreeCAD material cards and APIs
+ """
+
+ def setUp(self):
+ """Setup function to initialize test data"""
+ self.ModelManager = Materials.ModelManager()
+ self.MaterialManager = Materials.MaterialManager()
+ self.uuids = Materials.UUIDs()
+
+ # Use our test files as a custom directory
+ param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Material/Resources")
+ self.customDir = param.GetString("CustomMaterialsDir", "")
+ self.useBuiltInDir = param.GetBool("UseBuiltInMaterials", True)
+ self.useWorkbenchDir = param.GetBool("UseMaterialsFromWorkbenches", True)
+ self.useUserDir = param.GetBool("UseMaterialsFromConfigDir", True)
+ self.useCustomDir = param.GetBool("UseMaterialsFromCustomDir", False)
+
+ filePath = os.path.dirname(__file__) + os.sep
+ testPath = filePath + "Materials"
+ param.SetString("CustomMaterialsDir", testPath)
+ param.SetBool("UseBuiltInMaterials", False)
+ param.SetBool("UseMaterialsFromWorkbenches", False)
+ param.SetBool("UseMaterialsFromConfigDir", False)
+ param.SetBool("UseMaterialsFromCustomDir", True)
+
+ self.MaterialManager.refresh()
+
+ def tearDown(self):
+ param = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Material/Resources")
+
+ # Restore preferences
+ param.SetString("CustomMaterialsDir", self.customDir)
+ param.SetBool("UseBuiltInMaterials", self.useBuiltInDir)
+ param.SetBool("UseMaterialsFromWorkbenches", self.useWorkbenchDir)
+ param.SetBool("UseMaterialsFromConfigDir", self.useUserDir)
+ param.SetBool("UseMaterialsFromCustomDir", self.useCustomDir)
+
+ self.MaterialManager.refresh()
+
+ def testFilter(self):
+ """Test that our filter returns the correct materials"""
+
+ # First check that our materials are loading
+ material = self.MaterialManager.getMaterial(UUIDAluminumAppearance)
+ self.assertIsNotNone(material)
+ self.assertEqual(material.Name, "TestAluminumAppearance")
+ self.assertEqual(material.UUID, UUIDAluminumAppearance)
+
+ material = self.MaterialManager.getMaterial(UUIDAluminumMixed)
+ self.assertIsNotNone(material)
+ self.assertEqual(material.Name, "TestAluminumMixed")
+ self.assertEqual(material.UUID, UUIDAluminumMixed)
+
+ material = self.MaterialManager.getMaterial(UUIDAluminumPhysical)
+ self.assertIsNotNone(material)
+ self.assertEqual(material.Name, "TestAluminumPhysical")
+ self.assertEqual(material.UUID, UUIDAluminumPhysical)
+
+ material = self.MaterialManager.getMaterial(UUIDBrassAppearance)
+ self.assertIsNotNone(material)
+ self.assertEqual(material.Name, "TestBrassAppearance")
+ self.assertEqual(material.UUID, UUIDBrassAppearance)
+
+ # Create an empty filter
+ filter = Materials.MaterialFilter()
+ self.assertEqual(len(self.MaterialManager.MaterialLibraries), 1)
+
+ filtered = self.MaterialManager.filterMaterials(filter)
+ self.assertEquals(len(filtered), 4)
+
+ filtered = self.MaterialManager.filterMaterials(filter, includeLegacy=True)
+ self.assertEquals(len(filtered), 5)
+
+ # Create a basic rendering filter
+ filter.Name = "Basic Appearance"
+ filter.RequiredCompleteModels = [self.uuids.BasicRendering]
+
+ filtered = self.MaterialManager.filterMaterials(filter)
+ self.assertEquals(len(filtered), 3)
+
+ filtered = self.MaterialManager.filterMaterials(filter, includeLegacy=True)
+ self.assertEquals(len(filtered), 3)
+
+ # Create an advanced rendering filter
+ filter= Materials.MaterialFilter()
+ filter.Name = "Advanced Appearance"
+ filter.RequiredCompleteModels = [self.uuids.AdvancedRendering]
+
+ filtered = self.MaterialManager.filterMaterials(filter)
+ self.assertEquals(len(filtered), 0)
+
+ filtered = self.MaterialManager.filterMaterials(filter, includeLegacy=True)
+ self.assertEquals(len(filtered), 0)
+
+ # Create a Density filter
+ filter= Materials.MaterialFilter()
+ filter.Name = "Density"
+ filter.RequiredCompleteModels = [self.uuids.Density]
+
+ filtered = self.MaterialManager.filterMaterials(filter)
+ self.assertEquals(len(filtered), 2)
+
+ filtered = self.MaterialManager.filterMaterials(filter, includeLegacy=True)
+ self.assertEquals(len(filtered), 3)
+
+ # Create a Hardness filter
+ filter= Materials.MaterialFilter()
+ filter.Name = "Hardness"
+ filter.RequiredCompleteModels = [self.uuids.Hardness]
+
+ filtered = self.MaterialManager.filterMaterials(filter)
+ self.assertEquals(len(filtered), 0)
+
+ filtered = self.MaterialManager.filterMaterials(filter, includeLegacy=True)
+ self.assertEquals(len(filtered), 0)
+
+ # Create a Density and Basic Rendering filter
+ filter= Materials.MaterialFilter()
+ filter.Name = "Density and Basic Rendering"
+ filter.RequiredCompleteModels = [self.uuids.Density, self.uuids.BasicRendering]
+
+ filtered = self.MaterialManager.filterMaterials(filter)
+ self.assertEquals(len(filtered), 1)
+
+ filtered = self.MaterialManager.filterMaterials(filter, includeLegacy=True)
+ self.assertEquals(len(filtered), 1)
+
+ # Create a Linear Elastic filter
+ filter= Materials.MaterialFilter()
+ filter.Name = "Linear Elastic"
+ filter.RequiredCompleteModels = [self.uuids.LinearElastic]
+
+ filtered = self.MaterialManager.filterMaterials(filter)
+ self.assertEquals(len(filtered), 0)
+
+ filtered = self.MaterialManager.filterMaterials(filter, includeLegacy=True)
+ self.assertEquals(len(filtered), 0)
+
+ filter= Materials.MaterialFilter()
+ filter.Name = "Linear Elastic - incomplete"
+ filter.RequiredModels = [self.uuids.LinearElastic]
+
+ filtered = self.MaterialManager.filterMaterials(filter)
+ self.assertEquals(len(filtered), 2)
+
+ filtered = self.MaterialManager.filterMaterials(filter, includeLegacy=True)
+
+ def testErrorInput(self):
+
+ self.assertRaises(TypeError, self.MaterialManager.filterMaterials, [])