diff --git a/doc/python/magnum.meshtools.rst b/doc/python/magnum.meshtools.rst index d29b89e..b472bc7 100644 --- a/doc/python/magnum.meshtools.rst +++ b/doc/python/magnum.meshtools.rst @@ -47,6 +47,10 @@ :ref:`MeshPrimitive.LINE_LOOP`, :ref:`MeshPrimitive.TRIANGLE_STRIP` or :ref:`MeshPrimitive.TRIANGLE_FAN` +.. py:function:: magnum.meshtools.interleave + :raise AssertionError: If any attribute in :p:`extra` has the data size + different from :p:`mesh` vertex count + .. py:function:: magnum.meshtools.transform2d :raise KeyError: If :p:`mesh` doesn't have :ref:`trade.MeshAttribute.POSITION` of index :p:`id` (and in morph diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index 3d7db56..f7e6647 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -138,8 +138,9 @@ .. py:class:: magnum.trade.MeshAttributeData Associates a typed data view with a name, vertex format and other mesh - attribute properties. The data view can be either one-dimensional, for - example a NumPy array: + attribute properties, which can be subsequently put into a :ref:`MeshData` + instance, for example with :ref:`meshtools.interleave()`. The data view can + be either one-dimensional, for example a NumPy array: .. Just to verify the snippet below actually works (don't want the arrows diff --git a/src/python/magnum/meshtools.cpp b/src/python/magnum/meshtools.cpp index 4c6dcbd..27db3be 100644 --- a/src/python/magnum/meshtools.cpp +++ b/src/python/magnum/meshtools.cpp @@ -169,12 +169,25 @@ void meshtools(py::module_& m) { return MeshTools::generateIndices(mesh); }, "Convert a mesh to plain indexed lines or triangles", py::arg("mesh")) - .def("interleave", [](const Trade::MeshData& mesh, MeshTools::InterleaveFlag flags) { + /** @todo eugh, find a way w/o the STL vector */ + .def("interleave", [](const Trade::MeshData& mesh, const std::vector& extra, MeshTools::InterleaveFlag flags) { + for(std::size_t i = 0; i != extra.size(); ++i) { + const Trade::MeshAttributeData& attribute = extra[i]; + if(attribute.data().size() != mesh.vertexCount()) { + PyErr_Format(PyExc_AssertionError, "extra attribute %zu expected to have %u items but got %zu", i, mesh.vertexCount(), attribute.data().size()); + throw py::error_already_set{}; + } + } /** @todo check that the vertices/indices aren't impl-specific if the interleaved preservation is disabled, once it's possible to test */ - return MeshTools::interleave(mesh, {}, flags); - }, "Interleave mesh data", py::arg("mesh"), py::arg("flags") = MeshTools::InterleaveFlag::PreserveInterleavedAttributes) + return MeshTools::interleave(mesh, extra, flags); + }, "Interleave mesh data", py::arg("mesh"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("extra") = std::vector{}, + py::arg("flags") = MeshTools::InterleaveFlag::PreserveInterleavedAttributes) .def("copy", static_cast(MeshTools::copy), "Make an owned copy of the mesh", py::arg("mesh")) /** @todo check that the indices/vertices aren't impl-specific once it's possible to test */ diff --git a/src/python/magnum/test/test_meshtools.py b/src/python/magnum/test/test_meshtools.py index 4428058..f559f6c 100644 --- a/src/python/magnum/test/test_meshtools.py +++ b/src/python/magnum/test/test_meshtools.py @@ -23,6 +23,7 @@ # DEALINGS IN THE SOFTWARE. # +import array import os import sys import unittest @@ -211,11 +212,27 @@ class Interleave(unittest.TestCase): # Gap after normals not removed self.assertEqual(interleaved.attribute_stride(trade.MeshAttribute.POSITION), 12 + 12 + 8) - interleaved_packed = meshtools.interleave(mesh, meshtools.InterleaveFlags.NONE) + interleaved_packed = meshtools.interleave(mesh, flags=meshtools.InterleaveFlags.NONE) self.assertEqual(interleaved_packed.attribute_count(), 2) # Gap after normals removed self.assertEqual(interleaved_packed.attribute_stride(trade.MeshAttribute.POSITION), 12 + 8) + def test_extra(self): + interleaved = meshtools.interleave(primitives.plane_solid(), extra=[ + trade.MeshAttributeData(trade.MeshAttribute.OBJECT_ID, VertexFormat.UNSIGNED_SHORT, array.array('H', [3, 176, 2, 14])) + ]) + self.assertEqual(interleaved.attribute_count(), 3) + self.assertTrue(interleaved.has_attribute(trade.MeshAttribute.OBJECT_ID)) + self.assertEqual(interleaved.attribute_format(trade.MeshAttribute.OBJECT_ID), VertexFormat.UNSIGNED_SHORT) + self.assertEqual(list(interleaved.attribute(trade.MeshAttribute.OBJECT_ID)), [3, 176, 2, 14]) + + def test_extra_invalid(self): + with self.assertRaisesRegex(AssertionError, "extra attribute 1 expected to have 4 items but got 5"): + meshtools.interleave(primitives.plane_solid(), extra=[ + trade.MeshAttributeData(trade.MeshAttribute.CUSTOM(33), VertexFormat.UNSIGNED_BYTE, array.array('B', [3, 176, 2, 12])), + trade.MeshAttributeData(trade.MeshAttribute.OBJECT_ID, VertexFormat.UNSIGNED_SHORT, array.array('H', [3, 176, 2, 12, 6])) + ]) + class Copy(unittest.TestCase): def test(self): mesh = primitives.square_solid()