diff --git a/doc/python/magnum.meshtools.rst b/doc/python/magnum.meshtools.rst index a2695ae..74bbd44 100644 --- a/doc/python/magnum.meshtools.rst +++ b/doc/python/magnum.meshtools.rst @@ -26,6 +26,15 @@ .. py:function:: magnum.meshtools.compress_indices :raise AssertionError: If :p:`mesh` is not indexed +.. py:function:: magnum.meshtools.concatenate + :raise AssertionError: If :p:`meshes` is empty + :raise AssertionError: If any of the :p:`meshes` is + :ref:`MeshPrimitive.LINE_STRIP`, :ref:`MeshPrimitive.LINE_LOOP`, + :ref:`MeshPrimitive.TRIANGLE_STRIP` or + :ref:`MeshPrimitive.TRIANGLE_FAN` + :raise AssertionError: If all :p:`meshes` don't have the same + :ref:`MeshPrimitive` + .. py:function:: magnum.meshtools.duplicate :raise AssertionError: If :p:`mesh` is not indexed diff --git a/doc/python/pages/changelog.rst b/doc/python/pages/changelog.rst index 5630558..0b5ec76 100644 --- a/doc/python/pages/changelog.rst +++ b/doc/python/pages/changelog.rst @@ -102,8 +102,8 @@ Changelog - Fixed :ref:`platform.sdl2.Application.InputEvent.Modifier` and :ref:`platform.glfw.Application.InputEvent.Modifier` to behave properly as flags and not just as an enum -- Exposed :ref:`meshtools.compress_indices()`, :ref:`meshtools.duplicate()`, - :ref:`meshtools.filter_except_attributes()`, +- Exposed :ref:`meshtools.compress_indices()`, :ref:`meshtools.concatenate()`, + :ref:`meshtools.duplicate()`, :ref:`meshtools.filter_except_attributes()`, :ref:`meshtools.filter_only_attributes()`, :ref:`meshtools.generate_indices()`, :ref:`meshtools.interleave()`, :ref:`meshtools.owned()`, :ref:`meshtools.remove_duplicates()`, diff --git a/src/python/magnum/meshtools.cpp b/src/python/magnum/meshtools.cpp index 9e52cf4..7b8faa6 100644 --- a/src/python/magnum/meshtools.cpp +++ b/src/python/magnum/meshtools.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -86,6 +87,37 @@ void meshtools(py::module_& m) { py::kw_only{}, /* new in pybind11 2.6 */ #endif py::arg("at_least") = MeshIndexType::UnsignedShort) + /** @todo ew, expose Iterable directly */ + .def("concatenate", [](const std::vector>& meshes, MeshTools::InterleaveFlag flags) { + if(meshes.empty()) { + PyErr_SetString(PyExc_AssertionError, "expected at least one mesh"); + throw py::error_already_set{}; + } + const MeshPrimitive primitive = meshes[0].get().primitive(); + for(std::size_t i = 0; i != meshes.size(); ++i) { + const Trade::MeshData& mesh = meshes[i]; + if(mesh.primitive() == MeshPrimitive::LineStrip || + mesh.primitive() == MeshPrimitive::LineLoop || + mesh.primitive() == MeshPrimitive::TriangleStrip || + mesh.primitive() == MeshPrimitive::TriangleFan) + { + PyErr_SetString(PyExc_AssertionError, "invalid mesh primitive"); + throw py::error_already_set{}; + } + if(mesh.primitive() != primitive) { + PyErr_SetString(PyExc_AssertionError, "inconsistent mesh primitive"); + throw py::error_already_set{}; + } + } + /** @todo check that the indices/Vertices aren't impl-specific once + it's possible to test */ + /** @todo there's a lot more assertions re attribute formats, array + sizes, etc */ + + return MeshTools::concatenate(Containers::Iterable{meshes.data(), meshes.size(), sizeof(std::reference_wrapper), [](const void* data) -> const Trade::MeshData& { + return static_cast*>(data)->get(); + }}, flags); + }, "Concatenate meshes together", py::arg("meshes"), py::arg("flags") = MeshTools::InterleaveFlag::PreserveInterleavedAttributes) .def("duplicate", [](const Trade::MeshData& mesh) { if(!mesh.isIndexed()) { PyErr_SetString(PyExc_AssertionError, "the mesh is not indexed"); diff --git a/src/python/magnum/test/test_meshtools.py b/src/python/magnum/test/test_meshtools.py index e56a56b..8172c6d 100644 --- a/src/python/magnum/test/test_meshtools.py +++ b/src/python/magnum/test/test_meshtools.py @@ -45,6 +45,32 @@ class CompressIndices(unittest.TestCase): with self.assertRaisesRegex(AssertionError, "the mesh is not indexed"): meshtools.compress_indices(mesh) +class Concatenate(unittest.TestCase): + def test(self): + cube = primitives.cube_solid() + # It's a TRIANGLE_STRIP + plane = meshtools.generate_indices(primitives.plane_solid()) + concatenated = meshtools.concatenate([cube, plane]) + self.assertEqual(concatenated.vertex_count, cube.vertex_count + plane.vertex_count) + self.assertEqual(concatenated.index_count, cube.index_count + plane.index_count) + + def test_empty(self): + with self.assertRaisesRegex(AssertionError, "expected at least one mesh"): + meshtools.concatenate([]) + + def test_invalid_primitive(self): + with self.assertRaisesRegex(AssertionError, "invalid mesh primitive"): + meshtools.concatenate([primitives.cube_solid(), primitives.plane_solid()]) + # Should check that also for the first argument + with self.assertRaisesRegex(AssertionError, "invalid mesh primitive"): + meshtools.concatenate([primitives.plane_solid()]) + + def test_inconsistent_primitive(self): + with self.assertRaisesRegex(AssertionError, "inconsistent mesh primitive"): + meshtools.concatenate([primitives.cube_solid(), primitives.line3d()]) + with self.assertRaisesRegex(AssertionError, "inconsistent mesh primitive"): + meshtools.concatenate([primitives.line3d(), primitives.cube_solid()]) + class Duplicate(unittest.TestCase): def test(self): mesh = primitives.cube_solid()