diff --git a/src/Mod/Fem/CMakeLists.txt b/src/Mod/Fem/CMakeLists.txt index 1ca59bf495f8..88460c7304ff 100755 --- a/src/Mod/Fem/CMakeLists.txt +++ b/src/Mod/Fem/CMakeLists.txt @@ -52,6 +52,7 @@ SET(FemInOut_SRCS feminout/importInpMesh.py feminout/importToolsFem.py feminout/importVTKResults.py + feminout/importYamlJsonMesh.py feminout/importZ88Mesh.py feminout/importZ88O2Results.py feminout/readFenicsXDMF.py @@ -183,6 +184,7 @@ SET(FemTestsMesh_SRCS femtest/testfiles/mesh/tetra10_mesh.inp femtest/testfiles/mesh/tetra10_mesh.unv femtest/testfiles/mesh/tetra10_mesh.vtk + femtest/testfiles/mesh/tetra10_mesh.yml femtest/testfiles/mesh/tetra10_mesh.z88 ) diff --git a/src/Mod/Fem/Init.py b/src/Mod/Fem/Init.py index 124c46950f72..d0b465b44ab4 100644 --- a/src/Mod/Fem/Init.py +++ b/src/Mod/Fem/Init.py @@ -39,6 +39,13 @@ FreeCAD.addImportType("FEM mesh Fenics (*.xml *.xdmf)", "feminout.importFenicsMesh") FreeCAD.addExportType("FEM mesh Fenics (*.xml *.xdmf)", "feminout.importFenicsMesh") +FreeCAD.addImportType( + "FEM mesh YAML/JSON (*.meshyaml *.meshjson *.yaml *.json)", "feminout.importYamlJsonMesh" +) +FreeCAD.addExportType( + "FEM mesh YAML/JSON (*.meshyaml *.meshjson *.yaml *.json)", "feminout.importYamlJsonMesh" +) + FreeCAD.addImportType("FEM mesh Z88 (*i1.txt)", "feminout.importZ88Mesh") FreeCAD.addExportType("FEM mesh Z88 (*i1.txt)", "feminout.importZ88Mesh") diff --git a/src/Mod/Fem/TestFem.py b/src/Mod/Fem/TestFem.py index bbdfaa112f9e..03fa8380db28 100644 --- a/src/Mod/Fem/TestFem.py +++ b/src/Mod/Fem/TestFem.py @@ -117,6 +117,7 @@ ./bin/FreeCADCmd --run-test "femtest.testmesh.TestMeshEleTetra10.test_tetra10_inp" ./bin/FreeCADCmd --run-test "femtest.testmesh.TestMeshEleTetra10.test_tetra10_unv" ./bin/FreeCADCmd --run-test "femtest.testmesh.TestMeshEleTetra10.test_tetra10_vkt" +./bin/FreeCADCmd --run-test "femtest.testmesh.TestMeshEleTetra10.test_tetra10_yml" ./bin/FreeCADCmd --run-test "femtest.testmesh.TestMeshEleTetra10.test_tetra10_z88" ./bin/FreeCADCmd --run-test "femtest.testobject.TestObjectCreate.test_femobjects_make" ./bin/FreeCADCmd --run-test "femtest.testobject.TestObjectType.test_femobjects_type" @@ -185,6 +186,9 @@ import unittest unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName("femtest.testmesh.TestMeshEleTetra10.test_tetra10_vkt")) +import unittest +unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName("femtest.testmesh.TestMeshEleTetra10.test_tetra10_yml")) + import unittest unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName("femtest.testmesh.TestMeshEleTetra10.test_tetra10_z88")) diff --git a/src/Mod/Fem/feminout/importToolsFem.py b/src/Mod/Fem/feminout/importToolsFem.py index e8b069284d75..256d61f5a7e1 100644 --- a/src/Mod/Fem/feminout/importToolsFem.py +++ b/src/Mod/Fem/feminout/importToolsFem.py @@ -242,6 +242,96 @@ def make_femmesh( return mesh +def make_dict_from_femmesh( + femmesh +): + """ + Converts FemMesh into dictionary structure which can immediately used + from importToolsFem.make_femmesh(mesh_data) to create a valid FEM mesh. + """ + # this dict can be easily saved and reloaded by yaml + # see importYamlJasonMesh for a implementation + + mesh_data = {} + + seg2 = [] + seg3 = [] + + tri3 = [] + tri6 = [] + quad4 = [] + quad8 = [] + + tet4 = [] + tet10 = [] + hex8 = [] + hex20 = [] + pent6 = [] + pent15 = [] + + # associations for lengths of tuples to different + # edge, face, and volume elements + + len_to_edge = {2: seg2, 3: seg3} + len_to_face = {3: tri3, 6: tri6, 4: quad4, 8: quad8} + len_to_volume = { + 4: tet4, + 10: tet10, + 8: hex8, + 20: hex20, + 6: pent6, + 15: pent15 + } + + # analyze edges + + for e in femmesh.Edges: + t = femmesh.getElementNodes(e) + len_to_edge[len(t)].append((e, t)) + + # analyze faces + + for f in femmesh.Faces: + t = femmesh.getElementNodes(f) + len_to_face[len(t)].append((f, t)) + + # analyze volumes + + for v in femmesh.Volumes: + t = femmesh.getElementNodes(v) + len_to_volume[len(t)].append((v, t)) + + mesh_data = { + 'Nodes': dict([(k, (v.x, v.y, v.z)) + for (k, v) in femmesh.Nodes.items()]), + 'Seg2Elem': dict(seg2), + 'Seg3Elem': dict(seg3), + + 'Tria3Elem': dict(tri3), + 'Tria6Elem': dict(tri6), + 'Quad4Elem': dict(quad4), + 'Quad8Elem': dict(quad8), + + 'Tetra4Elem': dict(tet4), + 'Tetra10Elem': dict(tet10), + 'Hexa8Elem': dict(hex8), + 'Hexa20Elem': dict(hex20), + 'Penta6Elem': dict(pent6), + 'Penta15Elem': dict(pent15), + + 'Groups': dict([( + group_num, ( + femmesh.getGroupName(group_num), + femmesh.getGroupElements(group_num) + ) + ) for group_num in femmesh.Groups]) + + } + # no pyr5, pyr13? + # no groups? + return mesh_data + + def fill_femresult_mechanical( res_obj, result_set diff --git a/src/Mod/Fem/feminout/importYamlJsonMesh.py b/src/Mod/Fem/feminout/importYamlJsonMesh.py new file mode 100644 index 000000000000..678c2a60d8e5 --- /dev/null +++ b/src/Mod/Fem/feminout/importYamlJsonMesh.py @@ -0,0 +1,220 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2019 - Johannes Hartung * +# * * +# * 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. * +# * * +# * This program 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 program; if not, write to the Free Software * +# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * +# * USA * +# * * +# *************************************************************************** + +__title__ = "FreeCAD YAML and JSON mesh reader and writer" +__author__ = "Johannes Hartung" +__url__ = "http://www.freecadweb.org" + +## @package importYAMLJSONMesh +# \ingroup FEM +# \brief FreeCAD YAML and JSON Mesh reader and writer for FEM workbench + +import json +import os + +import FreeCAD +from . import importToolsFem + +has_yaml = True +try: + import yaml +except ImportError: + FreeCAD.Console.PrintMessage( + "No YAML available (import yaml failure), " + "yaml import/export won't work\n" + ) + has_yaml = False + + +# **************************************************************************** +# ********* generic FreeCAD import and export methods ************************ +# names are fix given from FreeCAD, these methods are called from FreeCAD +# they are set in FEM modules Init.py + +if open.__module__ == '__builtin__': + # because we'll redefine open below (Python2) + pyopen = open +elif open.__module__ == 'io': + # because we'll redefine open below (Python3) + pyopen = open + + +def open( + filename +): + '''called when freecad opens a file + a FEM mesh object is created in a new document''' + + docname = os.path.splitext(os.path.basename(filename))[0] + return insert(filename, docname) + + +def insert( + filename, + docname +): + '''called when freecad wants to import a file" + a FEM mesh object is created in a existing document''' + + try: + doc = FreeCAD.getDocument(docname) + except NameError: + doc = FreeCAD.newDocument(docname) + FreeCAD.ActiveDocument = doc + + import_yaml_json_mesh(filename) + return doc + + +def export(objectslist, fileString): + "called when freecad exports a file" + if len(objectslist) != 1: + FreeCAD.Console.PrintError( + "This exporter can only " + "export one object.\n") + return + obj = objectslist[0] + if not obj.isDerivedFrom("Fem::FemMeshObject"): + FreeCAD.Console.PrintError("No FEM mesh object selected.\n") + return + + write(fileString, obj.FemMesh) + + +# **************************************************************************** +# ********* module specific methods ****************************************** +# reader: +# - a method uses a FemMesh instance, creates the FEM mesh document object and +# returns this object +# - a method read the data from file creates FemMesh instance out of the +# FEM mesh dictionary. This instance is returned +# - a converts the raw read data into the FEM mesh dictionary which +# can be used to create a FemMesh instance +# +# +# writer: +# - a method directly writes a FemMesh to the mesh file + +# ********* reader *********************************************************** +def import_yaml_json_mesh( + fileString +): + """ + read a FemMesh from a yaml/json mesh file + insert a FreeCAD FEM Mesh object in the ActiveDocument + return the FEM mesh document object + """ + + mesh_name = os.path.basename(os.path.splitext(fileString)[0]) + + femmesh = read(fileString) + if femmesh: + mesh_object = FreeCAD.ActiveDocument.addObject( + 'Fem::FemMeshObject', + mesh_name + ) + mesh_object.FemMesh = femmesh + + return mesh_object + + +def read( + fileString +): + '''read a FemMesh from a yaml/json mesh file and return the FemMesh + ''' + # no document object is created, just the FemMesh is returned + + fileExtension = os.path.basename(os.path.splitext(fileString)[1]) + + raw_mesh_data = {} + if fileExtension.lower() == ".meshjson" or\ + fileExtension.lower() == ".json": + fp = pyopen(fileString, "rt") + raw_mesh_data = json.load(fp) + fp.close() + elif ( + fileExtension.lower() == ".meshyaml" + or fileExtension.lower() == ".meshyml" + or fileExtension.lower() == ".yaml" + or fileExtension.lower() == ".yml" + ) and has_yaml: + fp = pyopen(fileString, "rt") + raw_mesh_data = yaml.load(fp) + fp.close() + else: + FreeCAD.Console.PrintError( + "Unknown extension, " + "please select other importer.\n") + + FreeCAD.Console.PrintMessage("Converting indices to integer numbers ...") + mesh_data = convert_raw_data_to_mesh_data(raw_mesh_data) + FreeCAD.Console.PrintMessage("OK\n") + + return importToolsFem.make_femmesh(mesh_data) + + +def convert_raw_data_to_mesh_data( + raw_mesh_data +): + """ + Converts raw dictionary data from JSON or YAML file to proper dict + for importToolsFem.make_femmesh(mesh_data). This is necessary since + JSON and YAML save dict keys as strings while make_femmesh expects + integers. + """ + + mesh_data = {} + for (type_key, type_dict) in raw_mesh_data.items(): + if type_key.lower() != "groups": + mesh_data[type_key] = dict([ + (int(k), v) for (k, v) in type_dict.items() + ]) + return mesh_data + + +# ********* writer *********************************************************** +def write( + fileString, + fem_mesh +): + '''directly write a FemMesh to a yaml/json mesh file + fem_mesh: a FemMesh''' + + mesh_data = importToolsFem.make_dict_from_femmesh(fem_mesh) + + if fileString != "": + fileName, fileExtension = os.path.splitext(fileString) + if fileExtension.lower() == ".json" \ + or fileExtension.lower() == ".meshjson": + fp = pyopen(fileString, "wt") + json.dump(mesh_data, fp, indent=4) + fp.close() + elif ( + fileExtension.lower() == ".meshyaml" + or fileExtension.lower() == ".meshyml" + or fileExtension.lower() == ".yaml" + or fileExtension.lower() == ".yml" + ) and has_yaml: + fp = pyopen(fileString, "wt") + yaml.safe_dump(mesh_data, fp) + fp.close() diff --git a/src/Mod/Fem/femtest/testfiles/mesh/tetra10_mesh.yml b/src/Mod/Fem/femtest/testfiles/mesh/tetra10_mesh.yml new file mode 100644 index 000000000000..fb3b07e4bdc8 --- /dev/null +++ b/src/Mod/Fem/femtest/testfiles/mesh/tetra10_mesh.yml @@ -0,0 +1,25 @@ +Groups: {} +Hexa20Elem: {} +Hexa8Elem: {} +Nodes: + 1: [6.0, 12.0, 18.0] + 2: [0.0, 0.0, 18.0] + 3: [12.0, 0.0, 18.0] + 4: [6.0, 6.0, 0.0] + 5: [3.0, 6.0, 18.0] + 6: [6.0, 0.0, 18.0] + 7: [9.0, 6.0, 18.0] + 8: [6.0, 9.0, 9.0] + 9: [3.0, 3.0, 9.0] + 10: [9.0, 3.0, 9.0] +Penta15Elem: {} +Penta6Elem: {} +Quad4Elem: {} +Quad8Elem: {} +Seg2Elem: {} +Seg3Elem: {} +Tetra10Elem: + 1: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +Tetra4Elem: {} +Tria3Elem: {} +Tria6Elem: {} diff --git a/src/Mod/Fem/femtest/testmesh.py b/src/Mod/Fem/femtest/testmesh.py index c03cee1994fd..1039541d50eb 100644 --- a/src/Mod/Fem/femtest/testmesh.py +++ b/src/Mod/Fem/femtest/testmesh.py @@ -456,6 +456,32 @@ def test_tetra10_vkt( else: fcc_print('FEM_VTK post processing is disabled.') + # ******************************************************************************************** + def test_tetra10_yml( + self + ): + # tetra10 element: reading from and writing to yaml/json mesh file format + + file_extension = 'yml' + outfile, testfile = self.get_file_paths(file_extension) + + # TODO: implement yaml/json mesh reader writer method calls in C++ + # self.femmesh.write(outfile) # write the mesh + # femmesh_testfile = Fem.read(outfile) # read the mesh from written mesh + # femmesh_outfile = Fem.read(testfile) # read the mesh from test mesh + # directly use Python methods to read and write files + from feminout.importYamlJsonMesh import write + write(self.femmesh, outfile) + from feminout.importYamlJsonMesh import read + femmesh_testfile = read(outfile) + femmesh_outfile = read(testfile) + + self.compare_mesh_files( + femmesh_testfile, + femmesh_outfile, + file_extension + ) + # ******************************************************************************************** def test_tetra10_z88( self