From cade85c94fe00c3efa61fd5c19a1eb5caea773de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 28 Jan 2024 16:31:23 +0100 Subject: [PATCH] python: ability to pass extra attributes to meshtools.interleave(). Yay, finally it's (almost) possible to create custom meshes from pure Python. Except that there always has to be some initial mesh to add attributes to, and it's not yet possible to supply index data there. --- doc/python/magnum.meshtools.rst | 4 ++++ doc/python/magnum.trade.rst | 5 +++-- src/python/magnum/meshtools.cpp | 19 ++++++++++++++++--- src/python/magnum/test/test_meshtools.py | 19 ++++++++++++++++++- 4 files changed, 41 insertions(+), 6 deletions(-) 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()