From d753b4543e422c3ee8f550af2662ae444242cd6f Mon Sep 17 00:00:00 2001 From: Alexander Dielen <alexander.dielen@rwth-aachen.de> Date: Thu, 31 Oct 2019 12:25:38 +0100 Subject: [PATCH] methods to add multiple vertices and faces using numpy arrays --- src/Mesh.hh | 96 +++++++++++++++++++++++----------------- tests/test_add_numpy.py | 97 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+), 39 deletions(-) create mode 100644 tests/test_add_numpy.py diff --git a/src/Mesh.hh b/src/Mesh.hh index bf9f398..16ea5ad 100644 --- a/src/Mesh.hh +++ b/src/Mesh.hh @@ -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 //====================================================================== diff --git a/tests/test_add_numpy.py b/tests/test_add_numpy.py new file mode 100644 index 0000000..c79aee6 --- /dev/null +++ b/tests/test_add_numpy.py @@ -0,0 +1,97 @@ +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) -- GitLab