Commit d753b454 authored by Alexander Dielen's avatar Alexander Dielen

methods to add multiple vertices and faces using numpy arrays

parent 246fd1e2
Pipeline #12633 passed with stages
in 6 minutes and 7 seconds
......@@ -346,6 +346,50 @@ py::array_t<int> indices(Mesh& _self) {
return py::array_t<int>(shape, strides, indices, base);
}
template <class Mesh>
void add_vertices(Mesh& _self, py::array_t<typename Mesh::Point::value_type> _points) {
// return if _points is empty
if (_points.size() == 0) {
return;
}
// _points is not empty, throw if _points has wrong shape
if (_points.ndim() != 2 || _points.shape(1) != 3) {
PyErr_SetString(PyExc_RuntimeError, "Array 'points' must have shape (n, 3)");
throw py::error_already_set();
}
for (ssize_t i = 0; i < _points.shape(0); ++i) {
_self.add_vertex(typename Mesh::Point(_points.at(i, 0), _points.at(i, 1), _points.at(i, 2)));
}
}
template <class Mesh>
void add_faces(Mesh& _self, py::array_t<int> _faces) {
// return if _faces is empty
if (_self.n_vertices() < 3 || _faces.size() == 0) {
return;
}
// _faces is not empty, throw if _faces has wrong shape
if (_faces.ndim() != 2 || _faces.shape(1) < 3) {
PyErr_SetString(PyExc_RuntimeError, "Array 'face_vertex_indices' must have shape (n, m) with m > 2");
throw py::error_already_set();
}
for (ssize_t i = 0; i < _faces.shape(0); ++i) {
std::vector<OM::VertexHandle> vhandles;
for (ssize_t j = 0; j < _faces.shape(1); ++j) {
if (_faces.at(i, j) >= 0 && _faces.at(i, j) < _self.n_vertices()) {
vhandles.push_back(OM::VertexHandle(_faces.at(i, j)));
}
}
if (vhandles.size() >= 3) {
_self.add_face(vhandles);
}
}
}
/**
* This function template is used to expose mesh member functions that are only
* available for a specific type of mesh (i.e. they are available for polygon
......@@ -599,45 +643,8 @@ void expose_mesh(py::module& m, const char *_name) {
.def(py::init([](py::array_t<typename Point::value_type> _points, py::array_t<int> _faces) {
Mesh mesh;
// return if _points is empty
if (_points.size() == 0) {
return mesh;
}
// _points is not empty, throw if _points has wrong shape
if (_points.ndim() != 2 || _points.shape(1) != 3) {
PyErr_SetString(PyExc_RuntimeError, "Array 'points' must have shape (n, 3)");
throw py::error_already_set();
}
for (ssize_t i = 0; i < _points.shape(0); ++i) {
mesh.add_vertex(Point(_points.at(i, 0), _points.at(i, 1), _points.at(i, 2)));
}
// return if _faces is empty
if (_faces.size() == 0) {
return mesh;
}
// _faces is not empty, throw if _faces has wrong shape
if (_faces.ndim() != 2 || _faces.shape(1) < 3) {
PyErr_SetString(PyExc_RuntimeError, "Array 'face_vertex_indices' must have shape (n, m) with m > 2");
throw py::error_already_set();
}
for (ssize_t i = 0; i < _faces.shape(0); ++i) {
std::vector<OM::VertexHandle> vhandles;
for (ssize_t j = 0; j < _faces.shape(1); ++j) {
if (_faces.at(i, j) >= 0 && _faces.at(i, j) < _points.shape(0)) {
vhandles.push_back(OM::VertexHandle(_faces.at(i, j)));
}
}
if (vhandles.size() >= 3) {
mesh.add_face(vhandles);
}
}
add_vertices(mesh, _points);
add_faces(mesh, _faces);
return mesh;
}), py::arg("points"), py::arg("face_vertex_indices")=py::array_t<int>())
......@@ -1350,6 +1357,17 @@ void expose_mesh(py::module& m, const char *_name) {
.def("halfedge_edge_indices", &halfedge_other_indices<Mesh, FuncHalfedgeEdge>)
.def("he_indices", &halfedge_other_indices<Mesh, FuncHalfedgeEdge>)
//======================================================================
// numpy add vertices & faces
//======================================================================
.def("add_vertices", &add_vertices<Mesh>, py::arg("points"))
.def("add_faces", &add_faces<Mesh>, py::arg("face_vertex_indices"))
.def("resize_points", [](Mesh& _self, size_t _n_vertices) {
_self.resize(_n_vertices, _self.n_edges(), _self.n_faces());
})
//======================================================================
// new property interface: single item
//======================================================================
......
import unittest
import openmesh
import numpy as np
class AddNumPy(unittest.TestCase):
def setUp(self):
self.points = np.array([[0, 0, 0], [0, 1, 0], [1, 1, 0], [1, 0, 0]])
self.fv_indices = np.array([[2, 1, 0], [2, 0, 3]])
self.mesh = openmesh.TriMesh(self.points, self.fv_indices)
# Test setup:
# 1 === 2
# | / |
# | / |
# | / |
# 0 === 3
self.checkSetUp()
def checkSetUp(self):
self.assertEqual(self.mesh.n_vertices(), 4)
self.assertEqual(self.mesh.n_edges(), 5)
self.assertEqual(self.mesh.n_faces(), 2)
self.assertEqual(self.mesh.points().shape, (4, 3))
self.assertTrue(np.array_equal(self.mesh.points(), self.points))
self.assertTrue(np.array_equal(self.mesh.fv_indices(), self.fv_indices))
def checkAddVertices(self, new_points):
self.assertEqual(self.mesh.n_vertices(), 10)
self.assertEqual(self.mesh.n_edges(), 5)
self.assertEqual(self.mesh.n_faces(), 2)
self.assertEqual(self.mesh.points().shape, (10, 3))
self.assertTrue(np.array_equal(self.mesh.points()[:4], self.points))
self.assertTrue(np.array_equal(self.mesh.points()[4:], new_points))
self.assertTrue(np.array_equal(self.mesh.fv_indices(), self.fv_indices))
def checkAddVerticesAndFaces(self, new_points, new_faces):
self.assertEqual(self.mesh.n_vertices(), 6)
self.assertEqual(self.mesh.n_edges(), 9)
self.assertEqual(self.mesh.n_faces(), 4)
self.assertEqual(self.mesh.points().shape, (6, 3))
self.assertTrue(np.array_equal(self.mesh.points()[:4], self.points))
self.assertTrue(np.array_equal(self.mesh.points()[4:], new_points))
self.assertTrue(np.array_equal(self.mesh.fv_indices()[:2], self.fv_indices))
self.assertTrue(np.array_equal(self.mesh.fv_indices()[2:], new_faces))
def test_resize_points(self):
new_points = np.random.rand(6, 3)
self.mesh.resize_points(10)
self.mesh.points()[4:] = new_points
self.checkAddVertices(new_points)
def test_add_vertices(self):
new_points = np.random.rand(6, 3)
self.mesh.add_vertices(new_points)
self.checkAddVertices(new_points)
def test_add_vertices_wrong_shape(self):
with self.assertRaises(RuntimeError):
self.mesh.add_vertices(np.random.rand(6, 2))
self.checkSetUp()
with self.assertRaises(RuntimeError):
self.mesh.add_vertices(np.random.rand(6, 4))
self.checkSetUp()
with self.assertRaises(RuntimeError):
self.mesh.add_vertices(np.random.rand(6, 3, 3))
self.checkSetUp()
def test_add_vertices_empty_array(self):
self.mesh.add_vertices(np.random.rand(0, 3))
self.checkSetUp()
def test_add_faces(self):
new_vertices = [[2, 1, 0], [2, 0, 0]]
new_faces = [[4, 2, 3], [4, 3, 5]]
self.mesh.add_vertices(new_vertices)
self.mesh.add_faces(new_faces)
self.checkAddVerticesAndFaces(new_vertices, new_faces)
def test_add_faces_wrong_shape(self):
with self.assertRaises(RuntimeError):
self.mesh.add_faces(np.arange(10).reshape(5, 2))
self.checkSetUp()
with self.assertRaises(RuntimeError):
self.mesh.add_faces(np.arange(30).reshape(5, 3, 2))
self.checkSetUp()
def test_add_faces_empty_array(self):
self.mesh.add_faces(np.random.rand(0, 3))
self.checkSetUp()
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(AddNumPy)
unittest.TextTestRunner(verbosity=2).run(suite)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment