FEM: add methods to to edit mesh groups:

- add addGroup, addGroupElements, removeGroup to C++ mesh class
- expose the methods to Python
- add a unit test class
- update test commands file
- add test to fem test app module
This commit is contained in:
joha2
2020-05-22 11:46:38 +02:00
committed by Bernd Hahnebach
parent 39d520f672
commit bdfd0b10a7
7 changed files with 401 additions and 3 deletions

View File

@@ -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<std::string, SMDSAbs_ElementType> 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<int> 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<SMESHDS_Group*>(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<int>::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);
}

View File

@@ -32,6 +32,7 @@
#include <list>
#include <boost/shared_ptr.hpp>
#include <SMESH_Version.h>
#include <SMDSAbs_ElementType.hxx>
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<int>);
/// Remove group (Name due to similarity to SMESH basis functions)
bool removeGroup(int);
//@}
struct FemMeshInfo {
int numFaces;
int numNode;

View File

@@ -158,6 +158,37 @@
<UserDocu>Return a tuple of ElementIDs to a given group ID</UserDocu>
</Documentation>
</Methode>
<Methode Name="addGroup" Const="true">
<Documentation>
<UserDocu>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.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="addGroupElements" Const="true">
<Documentation>
<UserDocu>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.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="removeGroup" Const="true">
<Documentation>
<UserDocu>Remove a group with a given group ID
removeGroup(groupid)
groupid: int
Returns boolean.
</UserDocu>
</Documentation>
</Methode>
<Methode Name="getElementType" Const="true">
<Documentation>
<UserDocu>Return the element type of a given ID</UserDocu>

View File

@@ -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<Py_ssize_t> 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> int_ids;
for (std::set<Py_ssize_t>::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;

View File

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

View File

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

View File

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