diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 1751731e68..1ca59bf495 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -128,6 +128,7 @@ SET(FemTests_SRCS femtest/__init__.py femtest/testccxtools.py femtest/testcommon.py + femtest/testmaterial.py femtest/testmesh.py femtest/testobject.py femtest/testresult.py diff --git a/src/Mod/Fem/TestFem.py b/src/Mod/Fem/TestFem.py index 06eae37a5b..aa41bb3ee8 100644 --- a/src/Mod/Fem/TestFem.py +++ b/src/Mod/Fem/TestFem.py @@ -30,6 +30,7 @@ from femtest.testcommon import TestFemCommon from femtest.testobject import TestObjectCreate from femtest.testobject import TestObjectType +from femtest.testmaterial import TestMaterialUnits from femtest.testmesh import TestMeshCommon from femtest.testmesh import TestMeshEleTetra10 from femtest.testresult import TestResult @@ -73,6 +74,7 @@ unittest.TextTestRunner().run(mytest) # module ./bin/FreeCAD --run-test "femtest.testccxtools" ./bin/FreeCAD --run-test "femtest.testcommon" +./bin/FreeCAD --run-test "femtest.testmaterial" ./bin/FreeCAD --run-test "femtest.testmesh" ./bin/FreeCAD --run-test "femtest.testobject" ./bin/FreeCAD --run-test "femtest.testresult" @@ -96,6 +98,8 @@ gf() ./bin/FreeCADCmd --run-test "femtest.testccxtools.TestCcxTools.test_5_Flow1D_thermomech_analysis" ./bin/FreeCADCmd --run-test "femtest.testcommon.TestFemCommon.test_adding_refshaps" ./bin/FreeCADCmd --run-test "femtest.testcommon.TestFemCommon.test_pyimport_all_FEM_modules" +./bin/FreeCADCmd --run-test "femtest.testmaterial.TestMaterialUnits.test_known_quantity_units" +./bin/FreeCADCmd --run-test "femtest.testmaterial.TestMaterialUnits.test_material_card_quantities" ./bin/FreeCADCmd --run-test "femtest.testmesh.TestMeshCommon.test_mesh_seg2_python" ./bin/FreeCADCmd --run-test "femtest.testmesh.TestMeshCommon.test_mesh_seg3_python" ./bin/FreeCADCmd --run-test "femtest.testmesh.TestMeshCommon.test_unv_save_load" @@ -142,6 +146,12 @@ unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName("femtest.t import unittest unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName("femtest.testcommon.TestFemCommon.test_pyimport_all_FEM_modules")) +import unittest +unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName("femtest.testmaterial.TestMaterialUnits.test_known_quantity_units")) + +import unittest +unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName("femtest.testmaterial.TestMaterialUnits.test_material_card_quantities")) + import unittest unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName("femtest.testmesh.TestMeshCommon.test_mesh_seg2_python")) diff --git a/src/Mod/Fem/femguiobjects/_ViewProviderFemMaterial.py b/src/Mod/Fem/femguiobjects/_ViewProviderFemMaterial.py index a4dbf59c44..f8ae8eb9d5 100644 --- a/src/Mod/Fem/femguiobjects/_ViewProviderFemMaterial.py +++ b/src/Mod/Fem/femguiobjects/_ViewProviderFemMaterial.py @@ -255,10 +255,19 @@ class _TaskPanelFemMaterial: # print(self.material) if self.selectionWidget.has_equal_references_shape_types(): self.do_not_set_thermal_zeros() - self.obj.Material = self.material - self.obj.References = self.selectionWidget.references - self.recompute_and_set_back_all() - return True + from materialtools.cardutils import check_mat_units as checkunits + if checkunits(self.material) is True: + self.obj.Material = self.material + self.obj.References = self.selectionWidget.references + else: + error_message = ( + 'Due to some wrong material quantity units in the changed ' + 'material data, the task panel changes where not accepted.\n' + ) + FreeCAD.Console.PrintError(error_message) + QtGui.QMessageBox.critical(None, "Material data not changed", error_message) + self.recompute_and_set_back_all() + return True def reject(self): self.recompute_and_set_back_all() @@ -356,33 +365,43 @@ class _TaskPanelFemMaterial: # do not change the self.material # check if dict is not empty (do not use 'is True') if new_material_params: - self.material = new_material_params - self.card_path = self.get_material_card(self.material) - # print('card_path: ' + self.card_path) - self.check_material_keys() - self.set_mat_params_in_input_fields(self.material) - if not self.card_path: - FreeCAD.Console.PrintMessage( - "Material card chosen by the material editor " - "was not found in material directories.\n" - "Either the card does not exist or some material " - "parameter where changed in material editor.\n" - ) - if self.has_transient_mat is False: - self.add_transient_material() + # check material quantity units + from materialtools.cardutils import check_mat_units as checkunits + if checkunits(new_material_params) is True: + self.material = new_material_params + self.card_path = self.get_material_card(self.material) + # print('card_path: ' + self.card_path) + self.check_material_keys() + self.set_mat_params_in_input_fields(self.material) + if not self.card_path: + FreeCAD.Console.PrintMessage( + "Material card chosen by the material editor " + "was not found in material directories.\n" + "Either the card does not exist or some material " + "parameter where changed in material editor.\n" + ) + if self.has_transient_mat is False: + self.add_transient_material() + else: + self.set_transient_material() else: - self.set_transient_material() + # we found our exact material in self.materials dict :-) + FreeCAD.Console.PrintLog( + "Material card chosen by the material editor " + "was found in material directories. " + "The found material card will be used.\n" + ) + index = self.parameterWidget.cb_materials.findData(self.card_path) + # print(index) + # set the current material in the cb widget + self.choose_material(index) else: - # we found our exact material in self.materials dict :-) - FreeCAD.Console.PrintLog( - "Material card chosen by the material editor " - "was found in material directories. " - "The found material card will be used.\n" + error_message = ( + 'Due to some wrong material quantity units in data passed ' + 'by the material editor, the material data was not changed.\n' ) - index = self.parameterWidget.cb_materials.findData(self.card_path) - # print(index) - # set the current material in the cb widget - self.choose_material(index) + FreeCAD.Console.PrintError(error_message) + QtGui.QMessageBox.critical(None, "Material data not changed", error_message) else: FreeCAD.Console.PrintLog( 'No changes where made by the material editor.\n' diff --git a/src/Mod/Fem/femtest/testmaterial.py b/src/Mod/Fem/femtest/testmaterial.py new file mode 100644 index 0000000000..3c37dede96 --- /dev/null +++ b/src/Mod/Fem/femtest/testmaterial.py @@ -0,0 +1,90 @@ +# *************************************************************************** +# * Copyright (c) 2019 - FreeCAD Developers * +# * Author: Bernd Hahnebach * +# * * +# * 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 * +# * * +# ***************************************************************************/ + + +import FreeCAD +import unittest +from .utilstest import fcc_print + +from os.path import join + + +class TestMaterialUnits(unittest.TestCase): + fcc_print('import TestMaterialUnits') + + def setUp(self): + # init, is executed before every test + self.doc_name = "TestMaterialUnits" + try: + FreeCAD.setActiveDocument(self.doc_name) + except: + FreeCAD.newDocument(self.doc_name) + finally: + FreeCAD.setActiveDocument(self.doc_name) + self.active_doc = FreeCAD.ActiveDocument + + def test_known_quantity_units(self): + from materialtools.cardutils import get_known_material_quantity_parameter as knownquant + known_quantity_parameter = knownquant() + from materialtools.cardutils import check_parm_unit as checkparamunit + for param in known_quantity_parameter: + fcc_print('{}'.format(param)) + self.assertTrue( + checkparamunit(param), + 'Unit of quantity material parameter {} is not known by FreeCAD unit system.' + .format(param) + ) + + def test_material_card_quantities(self): + # test the value and unit of known quantity parameter from solid build in material cards + # keep in mind only if FreeCAD is installed all materials are copied + # TODO Fluid materials (are they installed?) + + # get build in materials + builtin_solid_mat_dir = join(FreeCAD.getResourceDir(), "Mod", "Material", "StandardMaterial") + fcc_print('{}'.format(builtin_solid_mat_dir)) + from materialtools.cardutils import add_cards_from_a_dir as addmats + materials, cards, icons = addmats({}, {}, {}, builtin_solid_mat_dir, '') + + # get known material quantity parameter + from materialtools.cardutils import get_known_material_quantity_parameter as knownquant + known_quantities = knownquant() + + # check param, value pairs + from materialtools.cardutils import check_value_unit as checkvalueunit + for mat in materials: + fcc_print('{}'.format(mat)) + for param, value in materials[mat].items(): + if param in known_quantities: + # fcc_print(' {} --> {}'.format(param, value)) + self.assertTrue( + checkvalueunit(param, value), + 'Unit of quantity {} from material parameter {} is wrong.' + .format(value, param) + ) + + def tearDown(self): + # clearance, is executed after every test + FreeCAD.closeDocument(self.doc_name) + pass diff --git a/src/Mod/Material/Templatematerial.yml b/src/Mod/Material/Templatematerial.yml index 286b013612..c4c985de5b 100644 --- a/src/Mod/Material/Templatematerial.yml +++ b/src/Mod/Material/Templatematerial.yml @@ -115,9 +115,9 @@ URL: 'https://en.wikipedia.org/wiki/Density' Description: "Density in [FreeCAD Density unit]" FractureToughness: - Type: 'Quantity' + Type: 'Float' URL: 'https://en.wikipedia.org/wiki/Fracture_toughness' - Description: " " + Description: "Unit MPa * m^0.5 is not possible ATM in FreeCAD nicht moeglich thus String. Keep in mind the unit is fixed MPa * m^0.5. https://github.com/FreeCAD/FreeCAD/pull/2156" PoissonRatio: Type: 'Float' URL: 'https://en.wikipedia.org/wiki/Poisson%27s_ratio' diff --git a/src/Mod/Material/materialtools/cardutils.py b/src/Mod/Material/materialtools/cardutils.py index 9f82a92ab0..81c6cfab4f 100644 --- a/src/Mod/Material/materialtools/cardutils.py +++ b/src/Mod/Material/materialtools/cardutils.py @@ -117,7 +117,7 @@ def output_resources(resources): # ***** card handling **************************************************************************** # used in material editor and FEM material task panels -def import_materials(category='Solid'): +def import_materials(category='Solid', template=False): resources = get_material_resources(category) @@ -136,7 +136,7 @@ def import_materials(category='Solid'): return (materials, cards, icons) -def add_cards_from_a_dir(materials, cards, icons, mat_dir, icon): +def add_cards_from_a_dir(materials, cards, icons, mat_dir, icon, template=False): # fill materials and icons import glob from importFCMat import read @@ -148,6 +148,8 @@ def add_cards_from_a_dir(materials, cards, icons, mat_dir, icon): for a_path in dir_path_list: mat_dict = read(a_path) card_name = os.path.splitext(os.path.basename(a_path))[0] + if (card_name == 'TEMPLATE') and (template is False): + continue if delete_duplicates is False: materials[a_path] = mat_dict cards[a_path] = card_name @@ -316,6 +318,20 @@ def get_source_path(): return source_dir +def get_known_material_quantity_parameter(): + # get the material quantity parameter from material card template + template_data = get_material_template() + known_quantities = [] + for group in template_data: + gname = list(group.keys())[0] # group dict has only one key + for prop_name in group[gname]: + prop_type = group[gname][prop_name]['Type'] + if prop_type == 'Quantity': + # print('{} --> {}'.format(prop_name, prop_type)) + known_quantities.append(prop_name) + return known_quantities + + # ***** debug known and not known material parameter ********************************************* def get_and_output_all_carddata(cards): print('\n\n\nSTART--get_and_output_all_carddata\n--------------------') @@ -410,6 +426,195 @@ def write_cards_to_path(cards_path, cards_data, write_group_section=True, write_ write(card_path, card_data, False) +# ***** material parameter units ********************************************* +def check_parm_unit(param): + # check if this parameter is known to FreeCAD unit system + from FreeCAD import Units + # FreeCAD.Console.PrintMessage('{}\n'.format(param)) + if hasattr(Units, param): + return True + else: + return False + + +def check_value_unit(param, value): + # check unit + from FreeCAD import Units + # FreeCAD.Console.PrintMessage('{} --> {}\n'.format(param, value)) + if hasattr(Units, param): + # get unit and other information known by FreeCAD for this parameter + unit = getattr(Units, param) + quantity = Units.Quantity(1, unit) + user_prefered_unit = quantity.getUserPreferred()[2] + # test unit from mat dict value + some_text = "Parameter: {} --> value: {} -->".format(param, value) + try: + param_value = Units.Quantity(value) + try: + user_unit = param_value.getValueAs(user_prefered_unit) + if user_unit: + return True + elif user_unit == 0: + FreeCAD.Console.PrintMessage( + '{} Value {} = 0 for {}\n' + .format(some_text, value, param) + ) + return True + else: + FreeCAD.Console.PrintError( + '{} Not known problem in unit conversion.\n' + .format(some_text) + ) + except ValueError: + unitproblem = value.split()[-1] + FreeCAD.Console.PrintError( + '{} Unit {} is known by FreeCAD, but wrong for parameter {}.\n' + .format(some_text, unitproblem, param) + ) + except: + FreeCAD.Console.PrintError( + '{} Not known problem.\n' + .format(some_text) + ) + except ValueError: + unitproblem = value.split()[-1] + FreeCAD.Console.PrintError( + '{} Unit {} is not known by FreeCAD.\n' + .format(some_text, unitproblem) + ) + except: + FreeCAD.Console.PrintError( + '{} Not known problem.\n' + .format(some_text) + ) + else: + FreeCAD.Console.PrintError( + 'Parameter {} is not known to FreeCAD unit system.\n' + .format(param) + ) + return False + + +def output_parm_unit_info(param): + # check unit + from FreeCAD import Units + FreeCAD.Console.PrintMessage('{}\n'.format(param)) + if hasattr(Units, param): + FreeCAD.Console.PrintMessage( + '\nParameter {} is known to FreeCAD unit system.' + .format(param) + ) + + # get unit and other information known by FreeCAD for this parameter + unit = getattr(Units, param) + FreeCAD.Console.PrintMessage( + '{}\n' + .format(unit) + ) + + quantity = Units.Quantity(1, unit) + FreeCAD.Console.PrintMessage( + '{}\n' + .format(quantity) + ) + + user_prefered_unit = quantity.getUserPreferred()[2] + FreeCAD.Console.PrintMessage( + '{}\n' + .format(user_prefered_unit) + ) + + else: + FreeCAD.Console.PrintMessage( + 'Parameter {} is NOT known to FreeCAD unit system.' + .format(param) + ) + + +def output_value_unit_info(param, value): + # check unit + from FreeCAD import Units + some_text = "Parameter: {} --> value: {} -->".format(param, value) + FreeCAD.Console.PrintMessage('{} unit information:'.format(some_text)) + if hasattr(Units, param): + + # get unit and other information known by FreeCAD for this parameter + unit = getattr(Units, param) + FreeCAD.Console.PrintMessage( + '{}\n' + .format(unit) + ) + + quantity = Units.Quantity(1, unit) + FreeCAD.Console.PrintMessage( + '{}\n' + .format(quantity) + ) + + user_prefered_unit = quantity.getUserPreferred()[2] + FreeCAD.Console.PrintMessage( + '{}\n' + .format(user_prefered_unit) + ) + + # test unit from mat dict value + try: + param_value = Units.Quantity(value) + try: + user_unit = param_value.getValueAs(user_prefered_unit) + FreeCAD.Console.PrintMessage( + '{} Value in preferred unit: {}\n' + .format(some_text, user_unit) + ) + except ValueError: + unitproblem = value.split()[-1] + FreeCAD.Console.PrintError( + '{} Unit {} is known by FreeCAD, but wrong for parameter {}.\n' + .format(some_text, unitproblem, param) + ) + except: + FreeCAD.Console.PrintError( + '{} Not known problem.\n' + .format(some_text) + ) + + except ValueError: + unitproblem = value.split()[-1] + FreeCAD.Console.PrintError( + '{} Unit {} is not known by FreeCAD.\n' + .format(some_text, unitproblem) + ) + + except: + FreeCAD.Console.PrintError( + '{} Not known problem.\n' + .format(some_text) + ) + + else: + FreeCAD.Console.PrintMessage( + 'Parameter {} is not known to FreeCAD unit system.' + .format(param) + ) + + +def check_mat_units(mat): + known_quantities = get_known_material_quantity_parameter() + # check if the param is a Quantity according card template, than chaeck unit + # print(known_quantities) + units_ok = True + for param, value in mat.items(): + if param in known_quantities: + if check_value_unit(param, value) is False: + if units_ok is True: + units_ok = False + else: + pass + # print('{} is not a known FreeCAD quantity.'.format(param)) + # print('') + return units_ok + + # ***** some code examples *********************************************************************** ''' # cards, params, icons and resources ********** @@ -426,13 +631,18 @@ outmats(getmats()[0]) outtrio(getmats()) -a,b,c=getmats() +a,b,c = getmats() +materials, cards, icons = getmats() # param template, header, template card ********** from materialtools.cardutils import get_material_template as gettemplate gettemplate() +gettemplate()[1]['General']['Description'] +gettemplate()[2]['Mechanical']['FractureToughness'] + + from materialtools.cardutils import get_material_template as gettemplate template_data=gettemplate() for group in template_data: @@ -483,4 +693,99 @@ writecards(getsrc() + '/src/Mod/Material/StandardMaterial/', cards_data, False) # last True writes the TEMPLATE card which has no mat params because they have no values writecards(getsrc() + '/src/Mod/Material/StandardMaterial/', cards_data, True, True) + +# material quantity parameter unit checks ********** +from materialtools.cardutils import output_parm_unit_info as unitinfo +unitinfo('YoungsModulus') +unitinfo('FractureToughness') +unitinfo('PoissonRatio') + +from materialtools.cardutils import check_parm_unit as checkparamunit +checkparamunit('YoungsModulus') +checkparamunit('FractureToughness') + +from materialtools.cardutils import output_value_unit_info as valueunitinfo +valueunitinfo('YoungsModulus', '1 MPa') +valueunitinfo('FractureToughness', '25') +valueunitinfo('Density', '1 kg/m^3') +valueunitinfo('Density', '0 kg/m^3') + +from materialtools.cardutils import check_value_unit as checkvalueunit +checkvalueunit('YoungsModulus', '1 MPa') +checkvalueunit('FractureToughness', '25') +checkvalueunit('Density', '1 kg/m^3') +checkvalueunit('Density', '0 kg/m^3') + +# test mat unit properties +mat = { + 'Name': 'Concrete', + 'AngleOfFriction' : '1 deg', + 'CompressiveStrength': '1 MPa', + 'Density': '1 kg/m^3', + 'ShearModulus' : '1 MPa', + 'UltimateTensileStrength' : '1 MPa', + 'YieldStrength' : '1 MPa', + 'YoungsModulus': '1 MPa', + 'SpecificHeat' : '1 J/kg/K', + 'ThermalConductivity' : '1 W/m/K', + 'ThermalExpansionCoefficient' : '1 mm/mm/K' +} +from materialtools.cardutils import check_mat_units as checkunits +checkunits(mat) + +# not known quantities, returns True too +mat = { + 'Name': 'Concrete', + 'FractureToughness' : '1', + 'PoissonRatio': '0.17' # no unit but important too, proof somehow too +} +from materialtools.cardutils import check_mat_units as checkunits +checkunits(mat) + +# wrong units +mat = { + 'Name': 'Concrete', + 'CompressiveStrength': '12356 MBa', # type on unit, means unit not knwn + 'YoungsModulus': '654321 m', # unit known, but wrong unit for this property +} +from materialtools.cardutils import check_mat_units as checkunits +checkunits(mat) + +# missing unit, returns False +mat = { + 'Name': 'Concrete', + 'YoungsModulus' : '1' +} +from materialtools.cardutils import check_mat_units as checkunits +checkunits(mat) + +# empty dict, returns True +from materialtools.cardutils import check_mat_units as checkunits +checkunits({}) + + +# some unit code ********** +from FreeCAD import Units +getattr(Units, 'Pressure') +Units.Pressure +Units.Quantity('25 MPa') +Units.Quantity('25 MPa').getValueAs('Pa') +Units.Quantity('25 MPa').getUserPreferred()[2] +Units.Quantity(25000, Units.Pressure) +Units.Quantity(25000, Units.Pressure).getValueAs('MPa') +Units.Unit('25 MPa') +Units.Unit(-1,1,-2,0,0,0,0,0) + +# base units +from FreeCAD import Units +Units.Length +Units.Mass +Units.TimeSpan +Units.ElectricCurrent +Units.Temperature +Units.AmountOfSubstance +Units.LuminousIntensity +Units.Angle + + '''