Material: Material Filter API (#14292)

* Naterial: Material Filter API

Adds a material filtering function to the
MaterialManager Python object

* Remove whitespace
This commit is contained in:
David Carter
2024-05-27 17:00:39 +00:00
committed by GitHub
parent cecb965f50
commit 1bcf8ac9dc
10 changed files with 404 additions and 0 deletions

View File

@@ -52,5 +52,15 @@
<UserDocu>Save the material in the specified library</UserDocu>
</Documentation>
</Methode>
<Methode Name="filterMaterials" Keyword="true">
<Documentation>
<UserDocu>Returns a filtered material list</UserDocu>
</Documentation>
</Methode>
<Methode Name="refresh">
<Documentation>
<UserDocu>Refreshes the material tree. Use sparingly as this is an expensive operation.</UserDocu>
</Documentation>
</Methode>
</PythonExport>
</GenerateMaterial>

View File

@@ -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<std::map<QString, std::shared_ptr<MaterialTreeNode>>>& 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<MaterialFilter>(*(static_cast<MaterialFilterPy*>(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;
}

View File

@@ -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(

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -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"

View File

@@ -0,0 +1,192 @@
# **************************************************************************
# Copyright (c) 2023 David Carter <dcarter@davidcarter.ca> *
# *
# 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, [])