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