diff --git a/src/Mod/Fem/App/FemMesh.cpp b/src/Mod/Fem/App/FemMesh.cpp index cff50ef6bf..1cba782789 100644 --- a/src/Mod/Fem/App/FemMesh.cpp +++ b/src/Mod/Fem/App/FemMesh.cpp @@ -2053,3 +2053,69 @@ Base::Quantity FemMesh::getVolume(void)const } + +int FemMesh::addGroup(const std::string TypeString, const std::string Name, const int theId) +{ + // define mapping between typestring and ElementType + // TODO: remove code doubling by providing mappings for all FemMesh functions + typedef std::map string_eltype_map; + string_eltype_map mapping; + mapping["All"] = SMDSAbs_All; + mapping["Node"] = SMDSAbs_Node; + mapping["Edge"] = SMDSAbs_Edge; + mapping["Face"] = SMDSAbs_Face; + mapping["Volume"] = SMDSAbs_Volume; + mapping["0DElement"] = SMDSAbs_0DElement; + mapping["Ball"] = SMDSAbs_Ball; + + int aId = theId; + + // check whether typestring is valid + bool typeStringValid = false; + for (string_eltype_map::const_iterator it = mapping.begin(); it != mapping.end(); ++it) + { + std::string key = it->first; + if (key == TypeString) + typeStringValid = true; + } + if (!typeStringValid) + throw std::runtime_error("AddGroup: Invalid type string! Allowed: All, Node, Edge, Face, Volume, 0DElement, Ball"); + // add group to mesh + SMESH_Group* group = this->getSMesh()->AddGroup(mapping[TypeString], Name.c_str(), aId); + if (!group) + throw std::runtime_error("AddGroup: Failed to create new group."); + return aId; +} + +void FemMesh::addGroupElements(const int GroupId, const std::set ElementIds) +{ + SMESH_Group* group = this->getSMesh()->GetGroup(GroupId); + if (!group) { + throw std::runtime_error("AddGroupElements: No group for given id."); + } + SMESHDS_Group* groupDS = dynamic_cast(group->GetGroupDS()); + // TODO: is this dynamic_cast OK? + + // Traverse the full mesh and add elements to group if id is in set 'ids' + // and if group type is compatible with element + SMDSAbs_ElementType aElementType = groupDS->GetType(); + + SMDS_ElemIteratorPtr aElemIter = this->getSMesh()->GetMeshDS()->elementsIterator(aElementType); + while (aElemIter->more()) { + const SMDS_MeshElement* aElem = aElemIter->next(); + std::set::iterator it; + it = ElementIds.find(aElem->GetID()); + if (it != ElementIds.end()) + { + // the element was in the list + if (!groupDS->Contains(aElem)) // check whether element is already in group + groupDS->Add(aElem); // if not, add it + } + } +} + +bool FemMesh::removeGroup(int GroupId) +{ + return this->getSMesh()->RemoveGroup(GroupId); +} + diff --git a/src/Mod/Fem/App/FemMesh.h b/src/Mod/Fem/App/FemMesh.h index 6e8f51fbd0..63770e6b81 100644 --- a/src/Mod/Fem/App/FemMesh.h +++ b/src/Mod/Fem/App/FemMesh.h @@ -32,6 +32,7 @@ #include #include #include +#include class SMESH_Gen; class SMESH_Mesh; @@ -131,6 +132,17 @@ public: void transformGeometry(const Base::Matrix4D &rclMat); //@} + /** @name Group management */ + //@{ + /// Adds group to mesh + int addGroup(const std::string, const std::string, const int=-1); + /// Adds elements to group (int due to int used by raw SMESH functions) + void addGroupElements(int, std::set); + /// Remove group (Name due to similarity to SMESH basis functions) + bool removeGroup(int); + //@} + + struct FemMeshInfo { int numFaces; int numNode; diff --git a/src/Mod/Fem/App/FemMeshPy.xml b/src/Mod/Fem/App/FemMeshPy.xml index 30858b72a7..93c28a17cd 100755 --- a/src/Mod/Fem/App/FemMeshPy.xml +++ b/src/Mod/Fem/App/FemMeshPy.xml @@ -158,6 +158,37 @@ Return a tuple of ElementIDs to a given group ID + + + Add a group to mesh with specific name and type + addGroup(name, typestring, [id]) + name: string + typestring: \"All\", \"Node\", \"Edge\", \"Face\", \"Volume\", \"0DElement\", \"Ball\" + id: int + Optional id is used to force specific id for group, but does + not work, yet. + + + + + + Add a tuple of ElementIDs to a given group ID + addGroupElements(groupid, list_of_elements) + groupid: int + list_of_elements: list of int + Notice that the elements have to be in the mesh. + + + + + + Remove a group with a given group ID + removeGroup(groupid) + groupid: int + Returns boolean. + + + Return the element type of a given ID diff --git a/src/Mod/Fem/App/FemMeshPyImp.cpp b/src/Mod/Fem/App/FemMeshPyImp.cpp index 938505ec96..5334c3dad4 100644 --- a/src/Mod/Fem/App/FemMeshPyImp.cpp +++ b/src/Mod/Fem/App/FemMeshPyImp.cpp @@ -1033,6 +1033,103 @@ PyObject* FemMeshPy::getGroupElements(PyObject *args) return Py::new_reference_to(tuple); } +/* +Add Groups and elements to these. +*/ + +PyObject* FemMeshPy::addGroup(PyObject *args) +{ + // get name and typestring from arguments + char* Name; + char* typeString; + int theId = -1; + if (!PyArg_ParseTuple(args, "etet|i","utf-8", &Name, "utf-8", &typeString, &theId)) + return 0; + std::string EncodedName = std::string(Name); + std::string EncodedTypeString = std::string(typeString); + + int retId = -1; + + try + { + retId = getFemMeshPtr()->addGroup(EncodedTypeString, EncodedName, theId); + } + catch (Standard_Failure& e) { + PyErr_SetString(Base::BaseExceptionFreeCADError, e.GetMessageString()); + return 0; + } + std::cout << "Added Group: Name: \'" << EncodedName << "\' Type: \'" << EncodedTypeString << "\' id: " << retId << std::endl; + +#if PY_MAJOR_VERSION >= 3 + return PyLong_FromLong(retId); +#else + return PyInt_FromLong(retId); +#endif +} + +PyObject* FemMeshPy::addGroupElements(PyObject *args) +{ + int id; + // the second object should be a list + // see https://stackoverflow.com/questions/22458298/extending-python-with-c-pass-a-list-to-pyarg-parsetuple + PyObject *pList; + PyObject *pItem; + Py_ssize_t n; + + if (!PyArg_ParseTuple(args, "iO!", &id, &PyList_Type, &pList)) + { + PyErr_SetString(PyExc_TypeError, "AddGroupElements: 2nd Parameter must be a list."); + return 0; + } + + std::set ids; + n = PyList_Size(pList); + std::cout << "AddGroupElements: num elements: " << n << " sizeof: " << sizeof(n) << std::endl; + for (Py_ssize_t i = 0; i < n; i++) { + pItem = PyList_GetItem(pList, i); +#if PY_MAJOR_VERSION >= 3 + if(!PyLong_Check(pItem)) { +#else + if(!PyInt_Check(pItem)) { +#endif + PyErr_SetString(PyExc_TypeError, "AddGroupElements: List items must be integers."); + return 0; + } +#if PY_MAJOR_VERSION >= 3 + ids.insert(PyLong_AsSsize_t(pItem)); +#else + ids.insert(PyInt_AsSsize_t(pItem)); +#endif + // Py_ssize_t transparently handles maximum array lengths on 32bit and 64bit machines + // See: https://www.python.org/dev/peps/pep-0353/ + } + + // Downcast Py_ssize_t to int to be compatible with SMESH functions + std::set int_ids; + for (std::set::iterator it = ids.begin(); it != ids.end(); ++it) + int_ids.insert(Py_SAFE_DOWNCAST(*it, Py_ssize_t, int)); + + try + { + getFemMeshPtr()->addGroupElements(id, int_ids); + } + catch (Standard_Failure& e) { + PyErr_SetString(Base::BaseExceptionFreeCADError, e.GetMessageString()); + return 0; + } + + Py_Return; +} + +PyObject* FemMeshPy::removeGroup(PyObject *args) +{ + int theId; + if (!PyArg_ParseTuple(args, "i", &theId)) + return 0; + return PyBool_FromLong((long)(getFemMeshPtr()->removeGroup(theId))); +} + + PyObject* FemMeshPy::getElementType(PyObject *args) { int id; diff --git a/src/Mod/Fem/TestFemApp.py b/src/Mod/Fem/TestFemApp.py index 20734c39e3..a0c12d3dff 100644 --- a/src/Mod/Fem/TestFemApp.py +++ b/src/Mod/Fem/TestFemApp.py @@ -32,9 +32,10 @@ from femtest.app.test_open import TestObjectOpen as FemTest05 from femtest.app.test_material import TestMaterialUnits as FemTest06 from femtest.app.test_mesh import TestMeshCommon as FemTest07 from femtest.app.test_mesh import TestMeshEleTetra10 as FemTest08 -from femtest.app.test_result import TestResult as FemTest09 -from femtest.app.test_ccxtools import TestCcxTools as FemTest10 -from femtest.app.test_solverframework import TestSolverFrameWork as FemTest11 +from femtest.app.test_mesh import TestMeshGroups as FemTest09 +from femtest.app.test_result import TestResult as FemTest10 +from femtest.app.test_ccxtools import TestCcxTools as FemTest11 +from femtest.app.test_solverframework import TestSolverFrameWork as FemTest12 # dummy usage to get flake8 and lgtm quiet False if FemTest01.__name__ else True @@ -48,3 +49,4 @@ False if FemTest08.__name__ else True False if FemTest09.__name__ else True False if FemTest10.__name__ else True False if FemTest11.__name__ else True +False if FemTest12.__name__ else True diff --git a/src/Mod/Fem/femtest/app/test_mesh.py b/src/Mod/Fem/femtest/app/test_mesh.py index 453da6a7f9..7e7150cba6 100644 --- a/src/Mod/Fem/femtest/app/test_mesh.py +++ b/src/Mod/Fem/femtest/app/test_mesh.py @@ -529,3 +529,174 @@ class TestMeshEleTetra10(unittest.TestCase): femmesh_outfile, file_extension ) + + +# ************************************************************************************************ +# ************************************************************************************************ +# TODO: add elements to group with another type. Should be empty at the end. +class TestMeshGroups(unittest.TestCase): + fcc_print("import TestMeshGroups") + + # ******************************************************************************************** + def setUp( + self + ): + # setUp is executed before every test + + # new document + self.document = FreeCAD.newDocument(self.__class__.__name__) + + # ******************************************************************************************** + def tearDown( + self + ): + # tearDown is executed after every test + FreeCAD.closeDocument(self.document.Name) + + # ******************************************************************************************** + def test_00print( + self + ): + # since method name starts with 00 this will be run first + # this test just prints a line with stars + + fcc_print("\n{0}\n{1} run FEM TestMeshGroups tests {2}\n{0}".format( + 100 * "*", + 10 * "*", + 57 * "*" + )) + + # ******************************************************************************************** + def test_add_groups(self): + """ + Create different groups with different names. Check whether the + ids are correct, the names are correct, and whether the GroupCount is + correct. + """ + + from femexamples.meshes.mesh_canticcx_tetra10 import create_elements + from femexamples.meshes.mesh_canticcx_tetra10 import create_nodes + + fm = Fem.FemMesh() + control = create_nodes(fm) + if not control: + fcc_print("failed to create nodes") + control = create_elements(fm) + if not control: + fcc_print("failed to create elements") + + # information + # fcc_print(fm) + + expected_dict = {} + expected_dict["ids"] = [] + expected_dict["names"] = [ + "MyNodeGroup", + "MyEdgeGroup", + "MyVolumeGroup", + "My0DElementGroup", + "MyBallGroup" + ] + expected_dict["types"] = [ + "Node", + "Edge", + "Volume", + "0DElement", + "Ball" + ] + expected_dict["count"] = fm.GroupCount + 5 + result_dict = {} + + mygrpids = [] + for (name, typ) in zip(expected_dict["names"], expected_dict["types"]): + mygrpids.append(fm.addGroup(name, typ)) + + expected_dict["ids"] = sorted(tuple(mygrpids)) + + # fcc_print("expected dict") + # fcc_print(expected_dict) + + result_dict["count"] = fm.GroupCount + result_dict["ids"] = sorted(fm.Groups) + result_dict["types"] = list([fm.getGroupElementType(g) + for g in fm.Groups]) + result_dict["names"] = list([fm.getGroupName(g) for g in fm.Groups]) + + # fcc_print("result dict") + # fcc_print(result_dict) + + self.assertEqual( + expected_dict, + result_dict, + msg="expected: {0}\n\nresult: {1}\n\n differ".format(expected_dict, result_dict) + ) + + def test_delete_groups(self): + """ + Adds a number of groups to FemMesh and deletes them + afterwards. Checks whether GroupCount is OK + """ + from femexamples.meshes.mesh_canticcx_tetra10 import create_elements + from femexamples.meshes.mesh_canticcx_tetra10 import create_nodes + + fm = Fem.FemMesh() + control = create_nodes(fm) + if not control: + fcc_print("failed to create nodes") + control = create_elements(fm) + if not control: + fcc_print("failed to create elements") + + # information + # fcc_print(fm) + old_group_count = fm.GroupCount + myids = [] + for i in range(1000): + myids.append(fm.addGroup("group" + str(i), "Node")) + for grpid in myids: + fm.removeGroup(grpid) + new_group_count = fm.GroupCount + self.assertEqual( + old_group_count, + new_group_count, + msg=( + "GroupCount before and after adding and deleting groups differ: {0} != {1}" + .format(old_group_count, new_group_count) + ) + ) + + def test_add_group_elements(self): + """ + Add a node group, add elements to it. Verify that elements added + and elements in getGroupElements are the same. + """ + from femexamples.meshes.mesh_canticcx_tetra10 import create_elements + from femexamples.meshes.mesh_canticcx_tetra10 import create_nodes + + fm = Fem.FemMesh() + control = create_nodes(fm) + if not control: + fcc_print("failed to create nodes") + control = create_elements(fm) + if not control: + fcc_print("failed to create elements") + + # information + # fcc_print(fm) + + elements_to_be_added = [1, 2, 3, 4, 49, 64, 88, 100, 102, 188, 189, 190, 191] + myid = fm.addGroup("mynodegroup", "Node") + + # fcc_print(fm.getGroupElements(myid)) + + fm.addGroupElements(myid, elements_to_be_added) + elements_returned = list(fm.getGroupElements(myid)) # returns tuple + # fcc_print(elements_returned) + self.assertEqual( + elements_to_be_added, + elements_returned, + msg=( + "elements to be added {0} and elements returned {1} differ". + format(elements_to_be_added, elements_returned) + ) + ) diff --git a/src/Mod/Fem/femtest/test_commands.sh b/src/Mod/Fem/femtest/test_commands.sh index 190d88a268..4b7cfeb7a1 100644 --- a/src/Mod/Fem/femtest/test_commands.sh +++ b/src/Mod/Fem/femtest/test_commands.sh @@ -26,6 +26,7 @@ make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_femimport.TestObjectExistance make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_material.TestMaterialUnits make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshCommon make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshEleTetra10 +make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshGroups make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_object.TestObjectCreate make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_object.TestObjectType make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_open.TestObjectOpen @@ -61,6 +62,9 @@ make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshEleTetra10.test_t make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshEleTetra10.test_tetra10_vkt make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshEleTetra10.test_tetra10_yml make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshEleTetra10.test_tetra10_z88 +make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshGroups.test_add_groups +make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshGroups.test_delete_groups +make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_mesh.TestMeshGroups.test_add_group_elements make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_object.TestObjectCreate.test_femobjects_make make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_object.TestObjectType.test_femobjects_type make -j 4 && ./bin/FreeCADCmd -t femtest.app.test_object.TestObjectType.test_femobjects_isoftype @@ -215,6 +219,21 @@ unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName( 'femtest.app.test_mesh.TestMeshEleTetra10.test_tetra10_z88' )) +import unittest +unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName( + 'femtest.app.test_mesh.TestMeshGroups.test_add_groups' +)) + +import unittest +unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName( + 'femtest.app.test_mesh.TestMeshGroups.test_delete_groups' +)) + +import unittest +unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName( + 'femtest.app.test_mesh.TestMeshGroups.test_add_group_elements' +)) + import unittest unittest.TextTestRunner().run(unittest.TestLoader().loadTestsFromName( 'femtest.app.test_object.TestObjectCreate.test_femobjects_make'