diff --git a/doc/python/magnum.meshtools.rst b/doc/python/magnum.meshtools.rst index 74bbd44..8b31da5 100644 --- a/doc/python/magnum.meshtools.rst +++ b/doc/python/magnum.meshtools.rst @@ -38,6 +38,10 @@ .. py:function:: magnum.meshtools.duplicate :raise AssertionError: If :p:`mesh` is not indexed +.. py:function:: magnum.meshtools.filter_attributes + :raise AssertionError: If size of :p:`attributes_to_keep` is different than + :p:`mesh` attribute count + .. py:function:: magnum.meshtools.generate_indices :raise AssertionError: If :p:`mesh` is not :ref:`MeshPrimitive.LINE_STRIP`, :ref:`MeshPrimitive.LINE_LOOP`, :ref:`MeshPrimitive.TRIANGLE_STRIP` or diff --git a/doc/python/pages/changelog.rst b/doc/python/pages/changelog.rst index d699607..631f3c0 100644 --- a/doc/python/pages/changelog.rst +++ b/doc/python/pages/changelog.rst @@ -109,6 +109,7 @@ Changelog as flags and not just as an enum - Exposed :ref:`meshtools.compress_indices()`, :ref:`meshtools.concatenate()`, :ref:`meshtools.copy()`, :ref:`meshtools.duplicate()`, + :ref:`meshtools.filter_attributes()`, :ref:`meshtools.filter_except_attributes()`, :ref:`meshtools.filter_only_attributes()`, :ref:`meshtools.generate_indices()`, :ref:`meshtools.interleave()`, diff --git a/src/python/magnum/meshtools.cpp b/src/python/magnum/meshtools.cpp index bfae664..8ab418d 100644 --- a/src/python/magnum/meshtools.cpp +++ b/src/python/magnum/meshtools.cpp @@ -25,8 +25,9 @@ #include #include /* for std::vector */ -#include #include +#include +#include #include #include #include @@ -131,6 +132,17 @@ void meshtools(py::module_& m) { return MeshTools::duplicate(mesh); }, "Duplicate indexed mesh data", py::arg("mesh")) + .def("filter_attributes", [](const Trade::MeshData& mesh, const Containers::BitArrayView attributesToKeep) { + if(attributesToKeep.size() != mesh.attributeCount()) { + PyErr_Format(PyExc_AssertionError, "expected %u bits but got %zu", mesh.attributeCount(), attributesToKeep.size()); + throw py::error_already_set{}; + } + + /* If the mesh already has an owner, use that instead to avoid + long reference chains */ + py::object meshOwner = pyObjectHolderFor(mesh).owner; + return Trade::pyDataHolder(MeshTools::filterAttributes(mesh, attributesToKeep), meshOwner.is_none() ? py::cast(mesh) : std::move(meshOwner)); + }, "Filter a mesh to contain only the selected subset of attributes", py::arg("mesh"), py::arg("attributes_to_keep")) .def("filter_except_attributes", [](const Trade::MeshData& mesh, const std::vector attributes) { /* If the mesh already has an owner, use that instead to avoid long reference chains */ diff --git a/src/python/magnum/test/test_meshtools.py b/src/python/magnum/test/test_meshtools.py index c3cbe21..4ad04ac 100644 --- a/src/python/magnum/test/test_meshtools.py +++ b/src/python/magnum/test/test_meshtools.py @@ -27,6 +27,7 @@ import os import sys import unittest +from corrade import containers from magnum import * from magnum import meshtools, primitives, trade @@ -105,6 +106,41 @@ class GenerateIndices(unittest.TestCase): meshtools.generate_indices(mesh) class Filter(unittest.TestCase): + def test(self): + mesh = primitives.cube_solid() + mesh_refcount = sys.getrefcount(mesh) + self.assertEqual(mesh.attribute_count(), 2) + self.assertTrue(mesh.has_attribute(trade.MeshAttribute.NORMAL)) + + attributes_to_keep = containers.BitArray.value_init(mesh.attribute_count()) + attributes_to_keep[mesh.attribute_id(trade.MeshAttribute.NORMAL)] = True + + filtered = meshtools.filter_attributes(mesh, attributes_to_keep) + filtered_refcount = sys.getrefcount(filtered) + self.assertEqual(filtered.attribute_count(), 1) + self.assertTrue(filtered.has_attribute(trade.MeshAttribute.NORMAL)) + self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1) + self.assertIs(filtered.owner, mesh) + + # Subsequent filtering will still reference the original mesh, not the + # intermediates + filtered2 = meshtools.filter_attributes(filtered, containers.BitArray.direct_init(filtered.attribute_count(), True)) + self.assertEqual(filtered2.attribute_count(), 1) + self.assertTrue(filtered2.has_attribute(trade.MeshAttribute.NORMAL)) + self.assertEqual(sys.getrefcount(filtered), filtered_refcount) + self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 2) + self.assertIs(filtered2.owner, mesh) + + del filtered + self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1) + + del filtered2 + self.assertEqual(sys.getrefcount(mesh), mesh_refcount) + + def test_invalid_size(self): + with self.assertRaisesRegex(AssertionError, "expected 2 bits but got 3"): + meshtools.filter_attributes(primitives.cube_solid(), containers.BitArray.value_init(3)) + def test_only_attributes(self): mesh = primitives.cube_solid() mesh_refcount = sys.getrefcount(mesh)