diff --git a/doc/python/magnum.meshtools.rst b/doc/python/magnum.meshtools.rst index b328c77..d29b89e 100644 --- a/doc/python/magnum.meshtools.rst +++ b/doc/python/magnum.meshtools.rst @@ -49,38 +49,44 @@ .. py:function:: magnum.meshtools.transform2d :raise KeyError: If :p:`mesh` doesn't have - :ref:`trade.MeshAttribute.POSITION` of index :p:`id` + :ref:`trade.MeshAttribute.POSITION` of index :p:`id` (and in morph + target :p:`morph_target_id` if not :py:`-1`) :raise AssertionError: If :ref:`trade.MeshAttribute.POSITION` are not 2D .. py:function:: magnum.meshtools.transform2d_in_place :raise AssertionError: If :p:`mesh` vertex data aren't :ref:`trade.DataFlags.MUTABLE` :raise KeyError: If :p:`mesh` doesn't have - :ref:`trade.MeshAttribute.POSITION` of index :p:`id` + :ref:`trade.MeshAttribute.POSITION` of index :p:`id` (and in morph + target :p:`morph_target_id` if not :py:`-1`) :raise AssertionError: If :ref:`trade.MeshAttribute.POSITION` are not :ref:`VertexFormat.VECTOR2` .. py:function:: magnum.meshtools.transform3d :raise KeyError: If :p:`mesh` doesn't have - :ref:`trade.MeshAttribute.POSITION` of index :p:`id` + :ref:`trade.MeshAttribute.POSITION` of index :p:`id` (and in morph + target :p:`morph_target_id` if not :py:`-1`) :raise AssertionError: If :ref:`trade.MeshAttribute.POSITION` are not 3D .. py:function:: magnum.meshtools.transform3d_in_place :raise AssertionError: If :p:`mesh` vertex data aren't :ref:`trade.DataFlags.MUTABLE` :raise KeyError: If :p:`mesh` doesn't have - :ref:`trade.MeshAttribute.POSITION` of index :p:`id` + :ref:`trade.MeshAttribute.POSITION` of index :p:`id` (and in morph + target :p:`morph_target_id` if not :py:`-1`) :raise AssertionError: If :ref:`trade.MeshAttribute.POSITION` are not :ref:`VertexFormat.VECTOR3` .. py:function:: magnum.meshtools.transform_texture_coordinates2d :raise KeyError: If :p:`mesh` doesn't have - :ref:`trade.MeshAttribute.TEXTURE_COORDINATES` of index :p:`id` + :ref:`trade.MeshAttribute.TEXTURE_COORDINATES` of index :p:`id` (and in + morph target :p:`morph_target_id` if not :py:`-1`) .. py:function:: magnum.meshtools.transform_texture_coordinates2d_in_place :raise AssertionError: If :p:`mesh` vertex data aren't :ref:`trade.DataFlags.MUTABLE` :raise KeyError: If :p:`mesh` doesn't have - :ref:`trade.MeshAttribute.TEXTURE_COORDINATES` of index :p:`id` + :ref:`trade.MeshAttribute.TEXTURE_COORDINATES` of index :p:`id` (and in + morph target :p:`morph_target_id` if not :py:`-1`) :raise AssertionError: If :ref:`trade.MeshAttribute.TEXTURE_COORDINATES` are not :ref:`VertexFormat.VECTOR2` diff --git a/doc/python/magnum.trade.rst b/doc/python/magnum.trade.rst index ae9d594..7d43880 100644 --- a/doc/python/magnum.trade.rst +++ b/doc/python/magnum.trade.rst @@ -209,9 +209,9 @@ .. py:function:: magnum.trade.MeshData.attribute_id(self, id: int) :raise IndexError: If :p:`id` is negative or not less than :ref:`attribute_count()` -.. py:function:: magnum.trade.MeshData.attribute_id(self, name: magnum.trade.MeshAttribute, id: int) +.. py:function:: magnum.trade.MeshData.attribute_id(self, name: magnum.trade.MeshAttribute, id: int, morph_target_id: int) :raise KeyError: If :p:`id` is negative or not less than - :ref:`attribute_count()` for :p:`name` + :ref:`attribute_count()` for :p:`name` and :p:`morph_target_id` Compared to the C++ API, there's no :dox:`Trade::MeshData::findAttributeId()`, the desired workflow is instead @@ -220,41 +220,41 @@ .. py:function:: magnum.trade.MeshData.attribute_format(self, id: int) :raise IndexError: If :p:`id` is negative or not less than :ref:`attribute_count()` -.. py:function:: magnum.trade.MeshData.attribute_format(self, name: magnum.trade.MeshAttribute, id: int) +.. py:function:: magnum.trade.MeshData.attribute_format(self, name: magnum.trade.MeshAttribute, id: int, morph_target_id: int) :raise KeyError: If :p:`id` is negative or not less than - :ref:`attribute_count()` for :p:`name` + :ref:`attribute_count()` for :p:`name` and :p:`morph_target_id` .. py:function:: magnum.trade.MeshData.attribute_offset(self, id: int) :raise IndexError: If :p:`id` is negative or not less than :ref:`attribute_count()` -.. py:function:: magnum.trade.MeshData.attribute_offset(self, name: magnum.trade.MeshAttribute, id: int) +.. py:function:: magnum.trade.MeshData.attribute_offset(self, name: magnum.trade.MeshAttribute, id: int, morph_target_id: int) :raise KeyError: If :p:`id` is negative or not less than - :ref:`attribute_count()` for :p:`name` + :ref:`attribute_count()` for :p:`name` and :p:`morph_target_id` .. py:function:: magnum.trade.MeshData.attribute_stride(self, id: int) :raise IndexError: If :p:`id` is negative or not less than :ref:`attribute_count()` -.. py:function:: magnum.trade.MeshData.attribute_stride(self, name: magnum.trade.MeshAttribute, id: int) +.. py:function:: magnum.trade.MeshData.attribute_stride(self, name: magnum.trade.MeshAttribute, id: int, morph_target_id: int) :raise KeyError: If :p:`id` is negative or not less than - :ref:`attribute_count()` for :p:`name` + :ref:`attribute_count()` for :p:`name` and :p:`morph_target_id` .. py:function:: magnum.trade.MeshData.attribute_array_size(self, id: int) :raise IndexError: If :p:`id` is negative or not less than :ref:`attribute_count()` -.. py:function:: magnum.trade.MeshData.attribute_array_size(self, name: magnum.trade.MeshAttribute, id: int) +.. py:function:: magnum.trade.MeshData.attribute_array_size(self, name: magnum.trade.MeshAttribute, id: int, morph_target_id: int) :raise KeyError: If :p:`id` is negative or not less than - :ref:`attribute_count()` for :p:`name` + :ref:`attribute_count()` for :p:`name` and :p:`morph_target_id` .. py:function:: magnum.trade.MeshData.attribute(self, id: int) :raise IndexError: If :p:`id` is negative or not less than :ref:`attribute_count()` -.. py:function:: magnum.trade.MeshData.attribute(self, name: magnum.trade.MeshAttribute, id: int) +.. py:function:: magnum.trade.MeshData.attribute(self, name: magnum.trade.MeshAttribute, id: int, morph_target_id: int) :raise KeyError: If :p:`id` is negative or not less than - :ref:`attribute_count()` for :p:`name` + :ref:`attribute_count()` for :p:`name` and :p:`morph_target_id` .. py:function:: magnum.trade.MeshData.mutable_attribute(self, id: int) :raise IndexError: If :p:`id` is negative or not less than :ref:`attribute_count()` :raise AttributeError: If :ref:`vertex_data_flags` doesn't contain :ref:`DataFlags.MUTABLE` -.. py:function:: magnum.trade.MeshData.mutable_attribute(self, name: magnum.trade.MeshAttribute, id: int) +.. py:function:: magnum.trade.MeshData.mutable_attribute(self, name: magnum.trade.MeshAttribute, id: int, morph_target_id: int) :raise KeyError: If :p:`id` is negative or not less than - :ref:`attribute_count()` for :p:`name` + :ref:`attribute_count()` for :p:`name` and :p:`morph_target_id` :raise AttributeError: If :ref:`vertex_data_flags` doesn't contain :ref:`DataFlags.MUTABLE` diff --git a/src/python/magnum/meshtools.cpp b/src/python/magnum/meshtools.cpp index 9c84777..761b90a 100644 --- a/src/python/magnum/meshtools.cpp +++ b/src/python/magnum/meshtools.cpp @@ -187,8 +187,8 @@ void meshtools(py::module_& m) { #endif py::arg("float_epsilon") = Math::TypeTraits::epsilon(), py::arg("double_epsilon") = Math::TypeTraits::epsilon()) - .def("transform2d", [](const Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id, MeshTools::InterleaveFlag flags) { - const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id); + .def("transform2d", [](const Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id, Int morphTargetId, MeshTools::InterleaveFlag flags) { + const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); if(!positionAttributeId) { PyErr_SetString(PyExc_KeyError, "position attribute not found"); throw py::error_already_set{}; @@ -200,15 +200,19 @@ void meshtools(py::module_& m) { /** @todo check that the positions aren't impl-specific once it's possible to test */ - return MeshTools::transform2D(mesh, transformation, id, flags); - }, "Transform 2D positions in a mesh data", py::arg("mesh"), py::arg("transformation"), py::arg("id") = 0, py::arg("flags") = MeshTools::InterleaveFlag::PreserveInterleavedAttributes) - .def("transform2d_in_place", [](Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id) { + return MeshTools::transform2D(mesh, transformation, id, morphTargetId, flags); + }, "Transform 2D positions in a mesh data", py::arg("mesh"), py::arg("transformation"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1, py::arg("flags") = MeshTools::InterleaveFlag::PreserveInterleavedAttributes) + .def("transform2d_in_place", [](Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id, Int morphTargetId) { if(!(mesh.vertexDataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AssertionError, "vertex data not mutable"); throw py::error_already_set{}; } - const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id); + const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); if(!positionAttributeId) { PyErr_SetString(PyExc_KeyError, "position attribute not found"); throw py::error_already_set{}; @@ -218,10 +222,14 @@ void meshtools(py::module_& m) { throw py::error_already_set{}; } - MeshTools::transform2DInPlace(mesh, transformation, id); - }, "Transform 2D positions in a mesh data in-place", py::arg("mesh"), py::arg("transformation"), py::arg("id") = 0) - .def("transform3d", [](const Trade::MeshData& mesh, const Matrix4& transformation, UnsignedInt id, MeshTools::InterleaveFlag flags) { - const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id); + MeshTools::transform2DInPlace(mesh, transformation, id, morphTargetId); + }, "Transform 2D positions in a mesh data in-place", py::arg("mesh"), py::arg("transformation"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1) + .def("transform3d", [](const Trade::MeshData& mesh, const Matrix4& transformation, UnsignedInt id, Int morphTargetId, MeshTools::InterleaveFlag flags) { + const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); if(!positionAttributeId) { PyErr_SetString(PyExc_KeyError, "position attribute not found"); throw py::error_already_set{}; @@ -233,15 +241,19 @@ void meshtools(py::module_& m) { /** @todo check that the positions, normals, ... aren't impl-specific once it's possible to test */ - return MeshTools::transform3D(mesh, transformation, id, flags); - }, "Transform 3D positions, normals, tangents and bitangents in a mesh data", py::arg("mesh"), py::arg("transformation"), py::arg("id") = 0, py::arg("flags") = MeshTools::InterleaveFlag::PreserveInterleavedAttributes) - .def("transform3d_in_place", [](Trade::MeshData& mesh, const Matrix4& transformation, UnsignedInt id) { + return MeshTools::transform3D(mesh, transformation, id, morphTargetId, flags); + }, "Transform 3D positions, normals, tangents and bitangents in a mesh data", py::arg("mesh"), py::arg("transformation"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1, py::arg("flags") = MeshTools::InterleaveFlag::PreserveInterleavedAttributes) + .def("transform3d_in_place", [](Trade::MeshData& mesh, const Matrix4& transformation, UnsignedInt id, Int morphTargetId) { if(!(mesh.vertexDataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AssertionError, "vertex data not mutable"); throw py::error_already_set{}; } - const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id); + const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); if(!positionAttributeId) { PyErr_SetString(PyExc_KeyError, "position attribute not found"); throw py::error_already_set{}; @@ -251,9 +263,9 @@ void meshtools(py::module_& m) { throw py::error_already_set{}; } - const Containers::Optional tangentAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Tangent, id); - const Containers::Optional bitangentAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Bitangent, id); - const Containers::Optional normalAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Normal, id); + const Containers::Optional tangentAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Tangent, id, morphTargetId); + const Containers::Optional bitangentAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Bitangent, id, morphTargetId); + const Containers::Optional normalAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Normal, id, morphTargetId); if(tangentAttributeId && (mesh.attributeFormat(*tangentAttributeId) != VertexFormat::Vector3 && mesh.attributeFormat(*tangentAttributeId) != VertexFormat::Vector4)) @@ -270,10 +282,14 @@ void meshtools(py::module_& m) { throw py::error_already_set{}; } - MeshTools::transform3DInPlace(mesh, transformation, id); - }, "Transform 3D position, normals, tangents and bitangents in a mesh data in-place", py::arg("mesh"), py::arg("transformation"), py::arg("id") = 0) - .def("transform_texture_coordinates2d", [](const Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id, MeshTools::InterleaveFlag flags) { - const Containers::Optional textureCoordinateAttributeId = mesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates, id); + MeshTools::transform3DInPlace(mesh, transformation, id, morphTargetId); + }, "Transform 3D position, normals, tangents and bitangents in a mesh data in-place", py::arg("mesh"), py::arg("transformation"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1) + .def("transform_texture_coordinates2d", [](const Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id, Int morphTargetId, MeshTools::InterleaveFlag flags) { + const Containers::Optional textureCoordinateAttributeId = mesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates, id, morphTargetId); if(!textureCoordinateAttributeId) { PyErr_SetString(PyExc_KeyError, "texture coordinates attribute not found"); throw py::error_already_set{}; @@ -281,15 +297,19 @@ void meshtools(py::module_& m) { /** @todo check that the texture coordinates aren't impl-specific once it's possible to test */ - return MeshTools::transformTextureCoordinates2D(mesh, transformation, id, flags); - }, "Transform 2D texture coordinates in a mesh data", py::arg("mesh"), py::arg("transformation"), py::arg("id") = 0, py::arg("flags") = MeshTools::InterleaveFlag::PreserveInterleavedAttributes) - .def("transform_texture_coordinates2d_in_place", [](Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id) { + return MeshTools::transformTextureCoordinates2D(mesh, transformation, id, morphTargetId, flags); + }, "Transform 2D texture coordinates in a mesh data", py::arg("mesh"), py::arg("transformation"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1, py::arg("flags") = MeshTools::InterleaveFlag::PreserveInterleavedAttributes) + .def("transform_texture_coordinates2d_in_place", [](Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id, Int morphTargetId) { if(!(mesh.vertexDataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AssertionError, "vertex data not mutable"); throw py::error_already_set{}; } - const Containers::Optional textureCoordinateAttributeId = mesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates, id); + const Containers::Optional textureCoordinateAttributeId = mesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates, id, morphTargetId); if(!textureCoordinateAttributeId) { PyErr_SetString(PyExc_KeyError, "texture coordinates attribute not found"); throw py::error_already_set{}; @@ -299,8 +319,12 @@ void meshtools(py::module_& m) { throw py::error_already_set{}; } - MeshTools::transformTextureCoordinates2DInPlace(mesh, transformation, id); - }, "Transform 2D texture coordinates in a mesh data in-place", py::arg("mesh"), py::arg("transformation"), py::arg("id") = 0); + MeshTools::transformTextureCoordinates2DInPlace(mesh, transformation, id, morphTargetId); + }, "Transform 2D texture coordinates in a mesh data in-place", py::arg("mesh"), py::arg("transformation"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1); } } diff --git a/src/python/magnum/test/test_meshtools.py b/src/python/magnum/test/test_meshtools.py index daf8989..3d997b5 100644 --- a/src/python/magnum/test/test_meshtools.py +++ b/src/python/magnum/test/test_meshtools.py @@ -248,6 +248,9 @@ class RemoveDuplicates(unittest.TestCase): self.assertEqual(single_point.vertex_count, 1) class Transform(unittest.TestCase): + # TODO test everything with explicit morph target once there's support in + # some importer + def test_2d(self): mesh = primitives.line2d() self.assertEqual(mesh.attribute(trade.MeshAttribute.POSITION)[0], (0.0, 0.0)) @@ -293,18 +296,33 @@ class Transform(unittest.TestCase): def test_no_attribute(self): mesh = meshtools.copy(primitives.square_solid(primitives.SquareFlags.TEXTURE_COORDINATES)) + # ID not found + with self.assertRaisesRegex(KeyError, "position attribute not found"): + meshtools.transform2d(mesh, Matrix3(), id=1) + with self.assertRaisesRegex(KeyError, "position attribute not found"): + meshtools.transform2d_in_place(mesh, Matrix3(), id=1) + with self.assertRaisesRegex(KeyError, "position attribute not found"): + meshtools.transform3d(mesh, Matrix4(), id=1) + with self.assertRaisesRegex(KeyError, "position attribute not found"): + meshtools.transform3d_in_place(mesh, Matrix4(), id=1) + with self.assertRaisesRegex(KeyError, "texture coordinates attribute not found"): + meshtools.transform_texture_coordinates2d(mesh, Matrix3(), id=1) + with self.assertRaisesRegex(KeyError, "texture coordinates attribute not found"): + meshtools.transform_texture_coordinates2d_in_place(mesh, Matrix3(), id=1) + + # Morph target not found with self.assertRaisesRegex(KeyError, "position attribute not found"): - meshtools.transform2d(mesh, Matrix3(), 1) + meshtools.transform2d(mesh, Matrix3(), morph_target_id=37) with self.assertRaisesRegex(KeyError, "position attribute not found"): - meshtools.transform2d_in_place(mesh, Matrix3(), 1) + meshtools.transform2d_in_place(mesh, Matrix3(), morph_target_id=37) with self.assertRaisesRegex(KeyError, "position attribute not found"): - meshtools.transform3d(mesh, Matrix4(), 1) + meshtools.transform3d(mesh, Matrix4(), morph_target_id=37) with self.assertRaisesRegex(KeyError, "position attribute not found"): - meshtools.transform3d_in_place(mesh, Matrix4(), 1) + meshtools.transform3d_in_place(mesh, Matrix4(), morph_target_id=37) with self.assertRaisesRegex(KeyError, "texture coordinates attribute not found"): - meshtools.transform_texture_coordinates2d(mesh, Matrix3(), 1) + meshtools.transform_texture_coordinates2d(mesh, Matrix3(), morph_target_id=37) with self.assertRaisesRegex(KeyError, "texture coordinates attribute not found"): - meshtools.transform_texture_coordinates2d_in_place(mesh, Matrix3(), 1) + meshtools.transform_texture_coordinates2d_in_place(mesh, Matrix3(), morph_target_id=37) def test_not_2d_not_3d(self): mesh2d = primitives.line2d() @@ -341,6 +359,8 @@ class Transform(unittest.TestCase): meshtools.transform2d_in_place(importer.mesh('packed positions'), Matrix3()) with self.assertRaisesRegex(AssertionError, "positions are not VECTOR3"): meshtools.transform3d_in_place(importer.mesh('packed positions'), Matrix4()) + # TODO test also with an explicit ID and morph target ID to verify it's + # correctly propagated with self.assertRaisesRegex(AssertionError, "normals are not VECTOR3"): meshtools.transform3d_in_place(importer.mesh('packed normals'), Matrix4()) with self.assertRaisesRegex(AssertionError, "tangents are not VECTOR3 or VECTOR4"): diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index 293b1d8..88e2c68 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -267,6 +267,7 @@ class MeshData(unittest.TestCase): self.assertEqual(mesh.vertex_count, 3) self.assertEqual(mesh.attribute_count(), 9) + self.assertEqual(mesh.attribute_count(morph_target_id=37), 0) # Attribute properties by ID self.assertEqual(mesh.attribute_name(2), trade.MeshAttribute.POSITION) @@ -292,11 +293,12 @@ class MeshData(unittest.TestCase): self.assertTrue(mesh.has_attribute(trade.MeshAttribute.COLOR)) self.assertTrue(mesh.has_attribute(trade.MeshAttribute.POSITION)) self.assertFalse(mesh.has_attribute(trade.MeshAttribute.TANGENT)) + self.assertFalse(mesh.has_attribute(trade.MeshAttribute.POSITION, morph_target_id=37)) self.assertEqual(mesh.attribute_count(trade.MeshAttribute.POSITION), 1) self.assertEqual(mesh.attribute_count(trade.MeshAttribute.TEXTURE_COORDINATES), 2) self.assertEqual(mesh.attribute_count(trade.MeshAttribute.TANGENT), 0) self.assertEqual(mesh.attribute_id(trade.MeshAttribute.POSITION), 2) - self.assertEqual(mesh.attribute_id(trade.MeshAttribute.TEXTURE_COORDINATES, 1), 4) + self.assertEqual(mesh.attribute_id(trade.MeshAttribute.TEXTURE_COORDINATES, id=1), 4) self.assertEqual(mesh.attribute_format(trade.MeshAttribute.COLOR), VertexFormat.VECTOR3UB_NORMALIZED) self.assertEqual(mesh.attribute_format(trade.MeshAttribute.OBJECT_ID), VertexFormat.UNSIGNED_INT) self.assertEqual(mesh.attribute_offset(trade.MeshAttribute.COLOR), 20) @@ -631,19 +633,35 @@ class MeshData(unittest.TestCase): # Access by existing name + OOB ID with self.assertRaises(KeyError): - mesh.attribute_id(trade.MeshAttribute.TEXTURE_COORDINATES, 2) + mesh.attribute_id(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) with self.assertRaises(KeyError): - mesh.attribute_format(trade.MeshAttribute.TEXTURE_COORDINATES, 2) + mesh.attribute_format(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) with self.assertRaises(KeyError): - mesh.attribute_offset(trade.MeshAttribute.TEXTURE_COORDINATES, 2) + mesh.attribute_offset(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) with self.assertRaises(KeyError): - mesh.attribute_stride(trade.MeshAttribute.TEXTURE_COORDINATES, 2) + mesh.attribute_stride(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) with self.assertRaises(KeyError): - mesh.attribute_array_size(trade.MeshAttribute.TEXTURE_COORDINATES, 2) + mesh.attribute_array_size(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) with self.assertRaises(KeyError): - mesh.attribute(trade.MeshAttribute.TEXTURE_COORDINATES, 2) + mesh.attribute(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) with self.assertRaises(KeyError): - mesh.mutable_attribute(trade.MeshAttribute.TEXTURE_COORDINATES, 2) + mesh.mutable_attribute(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) + + # Access by existing name + OOB morph target ID + with self.assertRaises(KeyError): + mesh.attribute_id(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) + with self.assertRaises(KeyError): + mesh.attribute_format(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) + with self.assertRaises(KeyError): + mesh.attribute_offset(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) + with self.assertRaises(KeyError): + mesh.attribute_stride(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) + with self.assertRaises(KeyError): + mesh.attribute_array_size(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) + with self.assertRaises(KeyError): + mesh.attribute(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) + with self.assertRaises(KeyError): + mesh.mutable_attribute(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) def test_attribute_access_array(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 9bc354c..2595036 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -955,12 +955,25 @@ void trade(py::module_& m) { }, "Mutable indices") .def_property_readonly("vertex_count", &Trade::MeshData::vertexCount, "Vertex count") /* Has to be a function instead of a property because there's an - overload taking a name */ + overload taking a morph target ID and an overload taking a name */ .def("attribute_count", static_cast(&Trade::MeshData::attributeCount), "Attribute array count") + .def("attribute_count", static_cast(&Trade::MeshData::attributeCount), "Attribute array count for given morph target", + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("morph_target_id")) /** @todo direct access to MeshAttributeData, once making custom MeshData is desired */ - .def("has_attribute", &Trade::MeshData::hasAttribute, "Whether the mesh has given attribute", py::arg("name")) - .def("attribute_count", static_cast(&Trade::MeshData::attributeCount), "Count of given named attribute", py::arg("name")) + .def("has_attribute", &Trade::MeshData::hasAttribute, "Whether the mesh has given attribute", py::arg("name"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("morph_target_id") = -1) + .def("attribute_count", static_cast(&Trade::MeshData::attributeCount), "Count of given named attribute", py::arg("name"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("morph_target_id") = -1) /* IMPORTANT: due to pybind11 behavioral differences on (already EOL'd) Python 3.7 the following overloads need to have the MeshAttribute @@ -974,12 +987,16 @@ void trade(py::module_& m) { } return self.attributeName(id); }, "Attribute name", py::arg("id")) - .def("attribute_id", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { - if(const Containers::Optional found = self.findAttributeId(name, id)) + .def("attribute_id", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { + if(const Containers::Optional found = self.findAttributeId(name, id, morphTargetId)) return *found; PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; - }, "Absolute ID of a named attribute", py::arg("name"), py::arg("id") = 0) + }, "Absolute ID of a named attribute", py::arg("name"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1) .def("attribute_id", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); @@ -987,12 +1004,16 @@ void trade(py::module_& m) { } return self.attributeId(id); }, "Attribute ID in a set of attributes of the same name", py::arg("id")) - .def("attribute_format", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { - if(const Containers::Optional found = self.findAttributeId(name, id)) + .def("attribute_format", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { + if(const Containers::Optional found = self.findAttributeId(name, id, morphTargetId)) return self.attributeFormat(*found); PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; - }, "Format of a named attribute", py::arg("name"), py::arg("id") = 0) + }, "Format of a named attribute", py::arg("name"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1) .def("attribute_format", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); @@ -1000,12 +1021,16 @@ void trade(py::module_& m) { } return self.attributeFormat(id); }, "Attribute format", py::arg("id")) - .def("attribute_offset", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { - if(const Containers::Optional found = self.findAttributeId(name, id)) + .def("attribute_offset", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { + if(const Containers::Optional found = self.findAttributeId(name, id, morphTargetId)) return self.attributeOffset(*found); PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; - }, "Offset of a named attribute", py::arg("name"), py::arg("id") = 0) + }, "Offset of a named attribute", py::arg("name"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1) .def("attribute_offset", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); @@ -1013,12 +1038,16 @@ void trade(py::module_& m) { } return self.attributeOffset(id); }, "Attribute offset", py::arg("id")) - .def("attribute_stride", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { - if(const Containers::Optional found = self.findAttributeId(name, id)) + .def("attribute_stride", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { + if(const Containers::Optional found = self.findAttributeId(name, id, morphTargetId)) return self.attributeStride(*found); PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; - }, "Stride of a named attribute", py::arg("name"), py::arg("id") = 0) + }, "Stride of a named attribute", py::arg("name"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1) .def("attribute_stride", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); @@ -1026,12 +1055,16 @@ void trade(py::module_& m) { } return self.attributeStride(id); }, "Attribute stride", py::arg("id")) - .def("attribute_array_size", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { - if(const Containers::Optional found = self.findAttributeId(name, id)) + .def("attribute_array_size", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { + if(const Containers::Optional found = self.findAttributeId(name, id, morphTargetId)) return self.attributeArraySize(*found); PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; - }, "Array size of a named attribute", py::arg("name"), py::arg("id") = 0) + }, "Array size of a named attribute", py::arg("name"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1) .def("attribute_array_size", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); @@ -1039,8 +1072,8 @@ void trade(py::module_& m) { } return self.attributeArraySize(id); }, "Attribute array size", py::arg("id")) - .def("attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { - const Containers::Optional found = self.findAttributeId(name, id); + .def("attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { + const Containers::Optional found = self.findAttributeId(name, id, morphTargetId); if(!found) { PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; @@ -1052,7 +1085,11 @@ void trade(py::module_& m) { throw py::error_already_set{}; } return meshAttributeView(self, *found, self.attribute(*found)); - }, "Data for given named attribute", py::arg("name"), py::arg("id") = 0) + }, "Data for given named attribute", py::arg("name"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1) .def("attribute", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError); @@ -1066,8 +1103,8 @@ void trade(py::module_& m) { } return meshAttributeView(self, id, self.attribute(id)); }, "Data for given attribute", py::arg("id")) - .def("mutable_attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) { - const Containers::Optional found = self.findAttributeId(name, id); + .def("mutable_attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { + const Containers::Optional found = self.findAttributeId(name, id, morphTargetId); if(!found) { PyErr_SetNone(PyExc_KeyError); throw py::error_already_set{}; @@ -1083,7 +1120,11 @@ void trade(py::module_& m) { throw py::error_already_set{}; } return meshAttributeView(self, *found, self.mutableAttribute(*found)); - }, "Mutable data for given named attribute", py::arg("name"), py::arg("id") = 0) + }, "Mutable data for given named attribute", py::arg("name"), + #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 + py::kw_only{}, /* new in pybind11 2.6 */ + #endif + py::arg("id") = 0, py::arg("morph_target_id") = -1) .def("mutable_attribute", [](Trade::MeshData& self, UnsignedInt id) { if(id >= self.attributeCount()) { PyErr_SetNone(PyExc_IndexError);