diff --git a/src/python/magnum/meshtools.cpp b/src/python/magnum/meshtools.cpp index 761b90a..fcf39c5 100644 --- a/src/python/magnum/meshtools.cpp +++ b/src/python/magnum/meshtools.cpp @@ -105,11 +105,11 @@ void meshtools(py::module_& m) { mesh.primitive() == MeshPrimitive::TriangleStrip || mesh.primitive() == MeshPrimitive::TriangleFan) { - PyErr_SetString(PyExc_AssertionError, "invalid mesh primitive"); + PyErr_Format(PyExc_AssertionError, "%S is not supported, turn it into a plain indexed mesh first", py::cast(mesh.primitive()).ptr()); throw py::error_already_set{}; } if(mesh.primitive() != primitive) { - PyErr_SetString(PyExc_AssertionError, "inconsistent mesh primitive"); + PyErr_Format(PyExc_AssertionError, "expected %S but got %S in mesh %zu", py::cast(primitive).ptr(), py::cast(mesh.primitive()).ptr(), i); throw py::error_already_set{}; } } @@ -161,7 +161,7 @@ void meshtools(py::module_& m) { mesh.primitive() != MeshPrimitive::TriangleStrip && mesh.primitive() != MeshPrimitive::TriangleFan) { - PyErr_SetString(PyExc_AssertionError, "invalid mesh primitive"); + PyErr_Format(PyExc_AssertionError, "invalid primitive %S", py::cast(mesh.primitive()).ptr()); throw py::error_already_set{}; } /** @todo check that the indices aren't impl-specific once it's @@ -190,11 +190,14 @@ void meshtools(py::module_& m) { .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"); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "the mesh has no positions with index %u", id); + else + PyErr_Format(PyExc_KeyError, "the mesh has no positions with index %u in morph target %i", id, morphTargetId); throw py::error_already_set{}; } if(vertexFormatComponentCount(mesh.attributeFormat(*positionAttributeId)) != 2) { - PyErr_SetString(PyExc_AssertionError, "positions are not 2D"); + PyErr_Format(PyExc_AssertionError, "expected 2D positions but got %S", py::cast(mesh.attributeFormat(*positionAttributeId)).ptr()); throw py::error_already_set{}; } /** @todo check that the positions aren't impl-specific once @@ -214,11 +217,14 @@ void meshtools(py::module_& m) { const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); if(!positionAttributeId) { - PyErr_SetString(PyExc_KeyError, "position attribute not found"); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "the mesh has no positions with index %u", id); + else + PyErr_Format(PyExc_KeyError, "the mesh has no positions with index %u in morph target %i", id, morphTargetId); throw py::error_already_set{}; } if(mesh.attributeFormat(*positionAttributeId) != VertexFormat::Vector2) { - PyErr_SetString(PyExc_AssertionError, "positions are not VECTOR2"); + PyErr_Format(PyExc_AssertionError, "expected %S positions but got %S", py::cast(VertexFormat::Vector2).ptr(), py::cast(mesh.attributeFormat(*positionAttributeId)).ptr()); throw py::error_already_set{}; } @@ -231,11 +237,14 @@ void meshtools(py::module_& m) { .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"); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "the mesh has no positions with index %u", id); + else + PyErr_Format(PyExc_KeyError, "the mesh has no positions with index %u in morph target %i", id, morphTargetId); throw py::error_already_set{}; } if(vertexFormatComponentCount(mesh.attributeFormat(*positionAttributeId)) != 3) { - PyErr_SetString(PyExc_AssertionError, "mesh positions are not 3D"); + PyErr_Format(PyExc_AssertionError, "expected 3D positions but got %S", py::cast(mesh.attributeFormat(*positionAttributeId)).ptr()); throw py::error_already_set{}; } /** @todo check that the positions, normals, ... aren't @@ -255,11 +264,14 @@ void meshtools(py::module_& m) { const Containers::Optional positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); if(!positionAttributeId) { - PyErr_SetString(PyExc_KeyError, "position attribute not found"); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "the mesh has no positions with index %u", id); + else + PyErr_Format(PyExc_KeyError, "the mesh has no positions with index %u in morph target %i", id, morphTargetId); throw py::error_already_set{}; } if(mesh.attributeFormat(*positionAttributeId) != VertexFormat::Vector3) { - PyErr_SetString(PyExc_AssertionError, "positions are not VECTOR3"); + PyErr_Format(PyExc_AssertionError, "expected %S positions but got %S", py::cast(VertexFormat::Vector3).ptr(), py::cast(mesh.attributeFormat(*positionAttributeId)).ptr()); throw py::error_already_set{}; } @@ -270,15 +282,15 @@ void meshtools(py::module_& m) { (mesh.attributeFormat(*tangentAttributeId) != VertexFormat::Vector3 && mesh.attributeFormat(*tangentAttributeId) != VertexFormat::Vector4)) { - PyErr_SetString(PyExc_AssertionError, "tangents are not VECTOR3 or VECTOR4"); + PyErr_Format(PyExc_AssertionError, "expected %S or %S tangents but got %S", py::cast(VertexFormat::Vector3).ptr(), py::cast(VertexFormat::Vector4).ptr(), py::cast(mesh.attributeFormat(*tangentAttributeId)).ptr()); throw py::error_already_set{}; } if(bitangentAttributeId && mesh.attributeFormat(*bitangentAttributeId) != VertexFormat::Vector3) { - PyErr_SetString(PyExc_AssertionError, "bitangents are not VECTOR3"); + PyErr_Format(PyExc_AssertionError, "expected %S bitangents but got %S", py::cast(VertexFormat::Vector3).ptr(), py::cast(mesh.attributeFormat(*bitangentAttributeId)).ptr()); throw py::error_already_set{}; } if(normalAttributeId && mesh.attributeFormat(*normalAttributeId) != VertexFormat::Vector3) { - PyErr_SetString(PyExc_AssertionError, "normals are not VECTOR3"); + PyErr_Format(PyExc_AssertionError, "expected %S normals but got %S", py::cast(VertexFormat::Vector3).ptr(), py::cast(mesh.attributeFormat(*normalAttributeId)).ptr()); throw py::error_already_set{}; } @@ -291,7 +303,10 @@ void meshtools(py::module_& m) { .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"); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "the mesh has no texture coordinates with index %u", id); + else + PyErr_Format(PyExc_KeyError, "the mesh has no texture coordinates with index %u in morph target %i", id, morphTargetId); throw py::error_already_set{}; } /** @todo check that the texture coordinates aren't impl-specific @@ -311,11 +326,14 @@ void meshtools(py::module_& m) { const Containers::Optional textureCoordinateAttributeId = mesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates, id, morphTargetId); if(!textureCoordinateAttributeId) { - PyErr_SetString(PyExc_KeyError, "texture coordinates attribute not found"); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "the mesh has no texture coordinates with index %u", id); + else + PyErr_Format(PyExc_KeyError, "the mesh has no texture coordinates with index %u in morph target %i", id, morphTargetId); throw py::error_already_set{}; } if(mesh.attributeFormat(*textureCoordinateAttributeId) != VertexFormat::Vector2) { - PyErr_SetString(PyExc_AssertionError, "texture coordinates are not VECTOR2"); + PyErr_Format(PyExc_AssertionError, "expected %S texture coordinates but got %S", py::cast(VertexFormat::Vector2).ptr(), py::cast(mesh.attributeFormat(*textureCoordinateAttributeId)).ptr()); throw py::error_already_set{}; } diff --git a/src/python/magnum/scenetools.cpp b/src/python/magnum/scenetools.cpp index a4c4b35..8bb3976 100644 --- a/src/python/magnum/scenetools.cpp +++ b/src/python/magnum/scenetools.cpp @@ -86,7 +86,7 @@ void scenetools(py::module_& m) { for(std::size_t i = 0; i != entriesToKeep.size(); ++i) { const Containers::Optional fieldId = scene.findFieldId(entriesToKeep[i].first()); if(!fieldId) { - PyErr_Format(PyExc_AssertionError, "field at index %zu not found", i, scene.fieldCount()); + PyErr_Format(PyExc_AssertionError, "%S not found among %u fields", py::cast(entriesToKeep[i].first()).ptr(), scene.fieldCount()); throw py::error_already_set{}; } if(usedFields[*fieldId]) { diff --git a/src/python/magnum/test/test_meshtools.py b/src/python/magnum/test/test_meshtools.py index 3d997b5..4428058 100644 --- a/src/python/magnum/test/test_meshtools.py +++ b/src/python/magnum/test/test_meshtools.py @@ -61,16 +61,16 @@ class Concatenate(unittest.TestCase): meshtools.concatenate([]) def test_invalid_primitive(self): - with self.assertRaisesRegex(AssertionError, "invalid mesh primitive"): + with self.assertRaisesRegex(AssertionError, "MeshPrimitive.TRIANGLE_STRIP is not supported, turn it into a plain indexed mesh first"): meshtools.concatenate([primitives.cube_solid(), primitives.plane_solid()]) # Should check that also for the first argument - with self.assertRaisesRegex(AssertionError, "invalid mesh primitive"): + with self.assertRaisesRegex(AssertionError, "MeshPrimitive.TRIANGLE_STRIP is not supported, turn it into a plain indexed mesh first"): meshtools.concatenate([primitives.plane_solid()]) def test_inconsistent_primitive(self): - with self.assertRaisesRegex(AssertionError, "inconsistent mesh primitive"): + with self.assertRaisesRegex(AssertionError, "expected MeshPrimitive.TRIANGLES but got MeshPrimitive.LINES in mesh 1"): meshtools.concatenate([primitives.cube_solid(), primitives.line3d()]) - with self.assertRaisesRegex(AssertionError, "inconsistent mesh primitive"): + with self.assertRaisesRegex(AssertionError, "expected MeshPrimitive.LINES but got MeshPrimitive.TRIANGLES in mesh 1"): meshtools.concatenate([primitives.line3d(), primitives.cube_solid()]) class Duplicate(unittest.TestCase): @@ -102,7 +102,7 @@ class GenerateIndices(unittest.TestCase): mesh = primitives.cube_solid() self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) - with self.assertRaisesRegex(AssertionError, "invalid mesh primitive"): + with self.assertRaisesRegex(AssertionError, "invalid primitive MeshPrimitive.TRIANGLES"): meshtools.generate_indices(mesh) class Filter(unittest.TestCase): @@ -297,44 +297,44 @@ class Transform(unittest.TestCase): mesh = meshtools.copy(primitives.square_solid(primitives.SquareFlags.TEXTURE_COORDINATES)) # ID not found - with self.assertRaisesRegex(KeyError, "position attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no positions with index 1"): meshtools.transform2d(mesh, Matrix3(), id=1) - with self.assertRaisesRegex(KeyError, "position attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no positions with index 1"): meshtools.transform2d_in_place(mesh, Matrix3(), id=1) - with self.assertRaisesRegex(KeyError, "position attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no positions with index 1"): meshtools.transform3d(mesh, Matrix4(), id=1) - with self.assertRaisesRegex(KeyError, "position attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no positions with index 1"): meshtools.transform3d_in_place(mesh, Matrix4(), id=1) - with self.assertRaisesRegex(KeyError, "texture coordinates attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no texture coordinates with index 1"): meshtools.transform_texture_coordinates2d(mesh, Matrix3(), id=1) - with self.assertRaisesRegex(KeyError, "texture coordinates attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no texture coordinates with index 1"): meshtools.transform_texture_coordinates2d_in_place(mesh, Matrix3(), id=1) # Morph target not found - with self.assertRaisesRegex(KeyError, "position attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no positions with index 0 in morph target 37"): meshtools.transform2d(mesh, Matrix3(), morph_target_id=37) - with self.assertRaisesRegex(KeyError, "position attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no positions with index 0 in morph target 37"): meshtools.transform2d_in_place(mesh, Matrix3(), morph_target_id=37) - with self.assertRaisesRegex(KeyError, "position attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no positions with index 0 in morph target 37"): meshtools.transform3d(mesh, Matrix4(), morph_target_id=37) - with self.assertRaisesRegex(KeyError, "position attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no positions with index 0 in morph target 37"): meshtools.transform3d_in_place(mesh, Matrix4(), morph_target_id=37) - with self.assertRaisesRegex(KeyError, "texture coordinates attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no texture coordinates with index 0 in morph target 37"): meshtools.transform_texture_coordinates2d(mesh, Matrix3(), morph_target_id=37) - with self.assertRaisesRegex(KeyError, "texture coordinates attribute not found"): + with self.assertRaisesRegex(KeyError, "the mesh has no texture coordinates with index 0 in morph target 37"): meshtools.transform_texture_coordinates2d_in_place(mesh, Matrix3(), morph_target_id=37) def test_not_2d_not_3d(self): mesh2d = primitives.line2d() mesh3d = primitives.line3d() - with self.assertRaisesRegex(AssertionError, "positions are not 2D"): + with self.assertRaisesRegex(AssertionError, "expected 2D positions but got VertexFormat.VECTOR3"): meshtools.transform2d(mesh3d, Matrix3()) - with self.assertRaisesRegex(AssertionError, "positions are not VECTOR2"): + with self.assertRaisesRegex(AssertionError, "expected VertexFormat.VECTOR2 positions but got VertexFormat.VECTOR3"): meshtools.transform2d_in_place(mesh3d, Matrix3()) - with self.assertRaisesRegex(AssertionError, "positions are not 3D"): + with self.assertRaisesRegex(AssertionError, "expected 3D positions but got VertexFormat.VECTOR2"): meshtools.transform3d(mesh2d, Matrix4()) - with self.assertRaisesRegex(AssertionError, "positions are not VECTOR3"): + with self.assertRaisesRegex(AssertionError, "expected VertexFormat.VECTOR3 positions but got VertexFormat.VECTOR2"): meshtools.transform3d_in_place(mesh2d, Matrix4()) def test_not_float(self): @@ -355,17 +355,17 @@ class Transform(unittest.TestCase): self.assertEqual(packed_texcoords.attribute(trade.MeshAttribute.TEXTURE_COORDINATES)[1], (-0.5, 0.0)) # TODO test 2D position with something that's actually 2D - with self.assertRaisesRegex(AssertionError, "positions are not VECTOR2"): + with self.assertRaisesRegex(AssertionError, "expected VertexFormat.VECTOR2 positions but got VertexFormat.VECTOR3US"): meshtools.transform2d_in_place(importer.mesh('packed positions'), Matrix3()) - with self.assertRaisesRegex(AssertionError, "positions are not VECTOR3"): + with self.assertRaisesRegex(AssertionError, "expected VertexFormat.VECTOR3 positions but got VertexFormat.VECTOR3US"): 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"): + with self.assertRaisesRegex(AssertionError, "expected VertexFormat.VECTOR3 normals but got VertexFormat.VECTOR3S_NORMALIZED"): meshtools.transform3d_in_place(importer.mesh('packed normals'), Matrix4()) - with self.assertRaisesRegex(AssertionError, "tangents are not VECTOR3 or VECTOR4"): + with self.assertRaisesRegex(AssertionError, "expected VertexFormat.VECTOR3 or VertexFormat.VECTOR4 tangents but got VertexFormat.VECTOR4S_NORMALIZED"): meshtools.transform3d_in_place(importer.mesh('packed tangents'), Matrix4()) - with self.assertRaisesRegex(AssertionError, "texture coordinates are not VECTOR2"): + with self.assertRaisesRegex(AssertionError, "expected VertexFormat.VECTOR2 texture coordinates but got VertexFormat.VECTOR2US_NORMALIZED"): meshtools.transform_texture_coordinates2d_in_place(importer.mesh('packed texcoords'), Matrix3()) def test_in_place_not_mutable(self): diff --git a/src/python/magnum/test/test_scenetools.py b/src/python/magnum/test/test_scenetools.py index df04eb6..1de4828 100644 --- a/src/python/magnum/test/test_scenetools.py +++ b/src/python/magnum/test/test_scenetools.py @@ -228,7 +228,7 @@ class Filter(unittest.TestCase): scenetools.filter_field_entries(scene, [ (8, containers.BitArrayView()) ]) - with self.assertRaisesRegex(AssertionError, "field at index 1 not found"): + with self.assertRaisesRegex(AssertionError, "SceneField.LIGHT not found among 8 fields"): scenetools.filter_field_entries(scene, [ (trade.SceneField.CAMERA, containers.BitArray.value_init(2)), (trade.SceneField.LIGHT, containers.BitArrayView()) diff --git a/src/python/magnum/test/test_trade.py b/src/python/magnum/test/test_trade.py index 7962e08..ea054f2 100644 --- a/src/python/magnum/test/test_trade.py +++ b/src/python/magnum/test/test_trade.py @@ -198,7 +198,7 @@ class ImageData(unittest.TestCase): image = importer.image2d(0) self.assertEqual(image.format, PixelFormat.DEPTH32F_STENCIL8UI) - with self.assertRaisesRegex(NotImplementedError, "access to this pixel format is not implemented yet, sorry"): + with self.assertRaisesRegex(NotImplementedError, "access to PixelFormat.DEPTH32F_STENCIL8UI is not implemented yet, sorry"): image.pixels class MeshData(unittest.TestCase): @@ -597,70 +597,75 @@ class MeshData(unittest.TestCase): mesh = importer.mesh(0) - # Access by OOB ID - with self.assertRaises(IndexError): + # Access by OOB ID. Deprecated build contains additional 2 backwards + # compatibility skinning attributes. + if magnum.BUILD_DEPRECATED: + indexOutOfRangeMessage = "index 11 out of range for 11 attributes" + else: + indexOutOfRangeMessage = "index 9 out of range for 9 attributes" + with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage): mesh.attribute_name(mesh.attribute_count()) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage): mesh.attribute_id(mesh.attribute_count()) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage): mesh.attribute_format(mesh.attribute_count()) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage): mesh.attribute_offset(mesh.attribute_count()) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage): mesh.attribute_stride(mesh.attribute_count()) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage): mesh.attribute_array_size(mesh.attribute_count()) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage): mesh.attribute(mesh.attribute_count()) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage): mesh.mutable_attribute(mesh.attribute_count()) # Access by nonexistent name - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TANGENT attributes"): mesh.attribute_id(trade.MeshAttribute.TANGENT) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TANGENT attributes"): mesh.attribute_format(trade.MeshAttribute.TANGENT) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TANGENT attributes"): mesh.attribute_offset(trade.MeshAttribute.TANGENT) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TANGENT attributes"): mesh.attribute_stride(trade.MeshAttribute.TANGENT) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TANGENT attributes"): mesh.attribute_array_size(trade.MeshAttribute.TANGENT) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TANGENT attributes"): mesh.attribute(trade.MeshAttribute.TANGENT) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TANGENT attributes"): mesh.mutable_attribute(trade.MeshAttribute.TANGENT) # Access by existing name + OOB ID - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 2 out of range for 2 MeshAttribute.TEXTURE_COORDINATES attributes"): mesh.attribute_id(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 2 out of range for 2 MeshAttribute.TEXTURE_COORDINATES attributes"): mesh.attribute_format(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 2 out of range for 2 MeshAttribute.TEXTURE_COORDINATES attributes"): mesh.attribute_offset(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 2 out of range for 2 MeshAttribute.TEXTURE_COORDINATES attributes"): mesh.attribute_stride(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 2 out of range for 2 MeshAttribute.TEXTURE_COORDINATES attributes"): mesh.attribute_array_size(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 2 out of range for 2 MeshAttribute.TEXTURE_COORDINATES attributes"): mesh.attribute(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 2 out of range for 2 MeshAttribute.TEXTURE_COORDINATES attributes"): mesh.mutable_attribute(trade.MeshAttribute.TEXTURE_COORDINATES, id=2) # Access by existing name + OOB morph target ID - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TEXTURE_COORDINATES attributes in morph target 37"): mesh.attribute_id(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TEXTURE_COORDINATES attributes in morph target 37"): mesh.attribute_format(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TEXTURE_COORDINATES attributes in morph target 37"): mesh.attribute_offset(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TEXTURE_COORDINATES attributes in morph target 37"): mesh.attribute_stride(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TEXTURE_COORDINATES attributes in morph target 37"): mesh.attribute_array_size(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TEXTURE_COORDINATES attributes in morph target 37"): mesh.attribute(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "index 0 out of range for 0 MeshAttribute.TEXTURE_COORDINATES attributes in morph target 37"): mesh.mutable_attribute(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37) def test_attribute_access_array(self): @@ -688,13 +693,13 @@ class MeshData(unittest.TestCase): mesh = importer.mesh(0) custom_attribute_id = mesh.attribute_id(custom_attribute) - with self.assertRaisesRegex(NotImplementedError, "access to this vertex format is not implemented yet, sorry"): + with self.assertRaisesRegex(NotImplementedError, "access to VertexFormat.MATRIX2X2 is not implemented yet, sorry"): mesh.attribute(custom_attribute_id) - with self.assertRaisesRegex(NotImplementedError, "access to this vertex format is not implemented yet, sorry"): + with self.assertRaisesRegex(NotImplementedError, "access to VertexFormat.MATRIX2X2 is not implemented yet, sorry"): mesh.mutable_attribute(custom_attribute_id) - with self.assertRaisesRegex(NotImplementedError, "access to this vertex format is not implemented yet, sorry"): + with self.assertRaisesRegex(NotImplementedError, "access to VertexFormat.MATRIX2X2 is not implemented yet, sorry"): mesh.attribute(custom_attribute) - with self.assertRaisesRegex(NotImplementedError, "access to this vertex format is not implemented yet, sorry"): + with self.assertRaisesRegex(NotImplementedError, "access to VertexFormat.MATRIX2X2 is not implemented yet, sorry"): mesh.mutable_attribute(custom_attribute) class SceneData(unittest.TestCase): @@ -1049,79 +1054,79 @@ class SceneData(unittest.TestCase): scene = importer.scene(0) # Access by OOB field ID - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.field_name(scene.field_count) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.field_flags(scene.field_count) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.field_type(scene.field_count) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.field_size(scene.field_count) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.field_array_size(scene.field_count) - with self.assertRaisesRegex(IndexError, "field out of range"): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.has_field_object(scene.field_count, 0) - with self.assertRaisesRegex(IndexError, "field out of range"): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.field_object_offset(scene.field_count, 0) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.mapping(scene.field_count) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.mutable_mapping(scene.field_count) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.field(scene.field_count) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "index 8 out of range for 8 fields"): scene.mutable_field(scene.field_count) # Access by nonexistent field name - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.field_id(trade.SceneField.SCALING) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.field_flags(trade.SceneField.SCALING) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.field_type(trade.SceneField.SCALING) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.field_size(trade.SceneField.SCALING) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.field_array_size(trade.SceneField.SCALING) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.has_field_object(trade.SceneField.SCALING, 0) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.field_object_offset(trade.SceneField.SCALING, 0) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.mapping(trade.SceneField.SCALING) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.mutable_mapping(trade.SceneField.SCALING) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.field(trade.SceneField.SCALING) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "SceneField.SCALING not found among 8 fields"): scene.mutable_field(trade.SceneField.SCALING) # OOB object ID - with self.assertRaisesRegex(IndexError, "object out of range"): + with self.assertRaisesRegex(IndexError, "index 4 out of range for 4 objects"): scene.has_field_object(0, 4) # PARENT - with self.assertRaisesRegex(IndexError, "object out of range"): + with self.assertRaisesRegex(IndexError, "index 4 out of range for 4 objects"): scene.has_field_object(trade.SceneField.PARENT, 4) - with self.assertRaisesRegex(IndexError, "object out of range"): + with self.assertRaisesRegex(IndexError, "index 4 out of range for 4 objects"): scene.field_object_offset(0, 4) # PARENT - with self.assertRaisesRegex(IndexError, "object out of range"): + with self.assertRaisesRegex(IndexError, "index 4 out of range for 4 objects"): scene.field_object_offset(trade.SceneField.PARENT, 4) # Lookup error - with self.assertRaises(LookupError): + with self.assertRaisesRegex(LookupError, "object 1 not found in field SceneField.CAMERA starting at offset 0"): scene.field_object_offset(4, 1) # CAMERA - with self.assertRaises(LookupError): + with self.assertRaisesRegex(LookupError, "object 1 not found in field SceneField.CAMERA starting at offset 0"): scene.field_object_offset(trade.SceneField.CAMERA, 1) # Lookup error due to field offset being at the end - with self.assertRaises(LookupError): + with self.assertRaisesRegex(LookupError, "object 1 not found in field SceneField.PARENT starting at offset 4"): scene.field_object_offset(0, 1, scene.field_size(0)) # PARENT - with self.assertRaises(LookupError): + with self.assertRaisesRegex(LookupError, "object 1 not found in field SceneField.PARENT starting at offset 4"): scene.field_object_offset(trade.SceneField.PARENT, 1, scene.field_size(trade.SceneField.PARENT)) # OOB field offset (offset == size is allowed, tested above) - with self.assertRaisesRegex(IndexError, "offset out of range"): + with self.assertRaisesRegex(IndexError, "offset 5 out of range for a field of size 4"): scene.field_object_offset(0, 1, scene.field_size(0) + 1) # PARENT - with self.assertRaisesRegex(IndexError, "offset out of range"): + with self.assertRaisesRegex(IndexError, "offset 5 out of range for a field of size 4"): scene.field_object_offset(trade.SceneField.PARENT, 1, scene.field_size(trade.SceneField.PARENT) + 1) def test_field_access_array(self): @@ -1138,13 +1143,13 @@ class SceneData(unittest.TestCase): scene = importer.scene(0) string_field_id = scene.field_id(string_field) - with self.assertRaisesRegex(NotImplementedError, "access to this scene field type is not implemented yet, sorry"): + with self.assertRaisesRegex(NotImplementedError, "access to SceneFieldType.STRING_OFFSET32 is not implemented yet, sorry"): scene.field(string_field_id) - with self.assertRaisesRegex(NotImplementedError, "access to this scene field type is not implemented yet, sorry"): + with self.assertRaisesRegex(NotImplementedError, "access to SceneFieldType.STRING_OFFSET32 is not implemented yet, sorry"): scene.mutable_field(string_field_id) - with self.assertRaisesRegex(NotImplementedError, "access to this scene field type is not implemented yet, sorry"): + with self.assertRaisesRegex(NotImplementedError, "access to SceneFieldType.STRING_OFFSET32 is not implemented yet, sorry"): scene.field(string_field) - with self.assertRaisesRegex(NotImplementedError, "access to this scene field type is not implemented yet, sorry"): + with self.assertRaisesRegex(NotImplementedError, "access to SceneFieldType.STRING_OFFSET32 is not implemented yet, sorry"): scene.mutable_field(string_field) class TextureData(unittest.TestCase): @@ -1397,50 +1402,54 @@ class Importer(unittest.TestCase): importer.image3d('') def test_index_oob(self): - importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') - importer.open_file(os.path.join(os.path.dirname(__file__), 'rgb.png')) - - with self.assertRaises(IndexError): - importer.scene_name(0) - with self.assertRaises(IndexError): - importer.object_name(0) - with self.assertRaises(IndexError): - importer.scene(0) - - with self.assertRaises(IndexError): - importer.mesh_level_count(0) - with self.assertRaises(IndexError): - importer.mesh_name(0) - with self.assertRaisesRegex(IndexError, "ID out of range"): - importer.mesh(0) - - with self.assertRaises(IndexError): - importer.texture_name(0) - with self.assertRaises(IndexError): - importer.texture(0) - - with self.assertRaises(IndexError): - importer.image1d_level_count(0) - with self.assertRaises(IndexError): - importer.image2d_level_count(1) - with self.assertRaises(IndexError): - importer.image3d_level_count(0) - - with self.assertRaises(IndexError): - importer.image1d_name(0) - with self.assertRaises(IndexError): - importer.image2d_name(1) - with self.assertRaises(IndexError): - importer.image3d_name(0) - - with self.assertRaisesRegex(IndexError, "ID out of range"): - importer.image1d(0) - with self.assertRaisesRegex(IndexError, "level out of range"): - importer.image2d(0, 1) - with self.assertRaisesRegex(IndexError, "ID out of range"): - importer.image2d(1) - with self.assertRaisesRegex(IndexError, "ID out of range"): - importer.image3d(0) + texture_importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + texture_importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) + + mesh_importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + mesh_importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) + + scene_importer = trade.ImporterManager().load_and_instantiate('GltfImporter') + scene_importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) + + with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"): + scene_importer.scene_name(3) + with self.assertRaisesRegex(IndexError, "index 5 out of range for 5 entries"): + scene_importer.object_name(5) + with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"): + scene_importer.scene(3) + + with self.assertRaisesRegex(IndexError, "index 5 out of range for 5 entries"): + mesh_importer.mesh_level_count(5) + with self.assertRaisesRegex(IndexError, "index 5 out of range for 5 entries"): + mesh_importer.mesh_name(5) + with self.assertRaisesRegex(IndexError, "index 5 out of range for 5 entries"): + mesh_importer.mesh(5) + + with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"): + texture_importer.texture_name(3) + with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"): + texture_importer.texture(3) + + with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"): + texture_importer.image1d_level_count(0) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 entries"): + texture_importer.image2d_level_count(2) + with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"): + texture_importer.image3d_level_count(0) + + with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"): + texture_importer.image1d_name(0) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 entries"): + texture_importer.image2d_name(2) + with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"): + texture_importer.image3d_name(0) + + with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"): + texture_importer.image1d(0) + with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 entries"): + texture_importer.image2d(2) + with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"): + texture_importer.image3d(0) def test_open_failed(self): importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') @@ -1493,7 +1502,7 @@ class Importer(unittest.TestCase): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "name Nonexistent not found among 3 entries"): importer.scene('Nonexistent') def test_scene_failed(self): @@ -1545,7 +1554,7 @@ class Importer(unittest.TestCase): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) - with self.assertRaises(IndexError): + with self.assertRaisesRegex(IndexError, "level 1 out of range for 1 entries"): importer.mesh(0, 1) def test_mesh_by_name(self): @@ -1559,14 +1568,14 @@ class Importer(unittest.TestCase): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "name Nonexistent not found among 5 entries"): importer.mesh('Nonexistent') def test_mesh_by_name_level_oob(self): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) - with self.assertRaisesRegex(IndexError, "level out of range"): + with self.assertRaisesRegex(IndexError, "level 1 out of range for 1 entries"): importer.mesh('Non-indexed mesh', 1) def test_mesh_failed(self): @@ -1589,7 +1598,7 @@ class Importer(unittest.TestCase): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "name Nonexistent not found among 3 entries"): importer.texture('Nonexistent') def test_texture_failed(self): @@ -1624,12 +1633,12 @@ class Importer(unittest.TestCase): del importer self.assertEqual(sys.getrefcount(manager), manager_refcount) - def test_image_level_oob(self): + def test_image2d_level_oob(self): # importer refcounting tested in image2d importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'rgb.png')) - with self.assertRaisesRegex(IndexError, "level out of range"): + with self.assertRaisesRegex(IndexError, "level 1 out of range for 1 entries"): importer.image2d(0, 1) def test_image2d_by_name(self): @@ -1643,7 +1652,7 @@ class Importer(unittest.TestCase): importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) - with self.assertRaises(KeyError): + with self.assertRaisesRegex(KeyError, "name Nonexistent not found among 2 entries"): importer.image2d('Nonexistent') def test_image2d_data(self): diff --git a/src/python/magnum/trade.cpp b/src/python/magnum/trade.cpp index 808ea28..bd4efc4 100644 --- a/src/python/magnum/trade.cpp +++ b/src/python/magnum/trade.cpp @@ -198,7 +198,7 @@ template Containers::PyArrayViewHolder formatStringGetitemSetitem = accessorsForPixelFormat(format); if(!formatStringGetitemSetitem.first()) { - PyErr_SetString(PyExc_NotImplementedError, "access to this pixel format is not implemented yet, sorry"); + PyErr_Format(PyExc_NotImplementedError, "access to %S is not implemented yet, sorry", py::cast(format).ptr()); throw py::error_already_set{}; } return Containers::pyArrayViewHolder(Containers::PyStridedArrayView{flattenPixelView(data, pixels), formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(image)); @@ -356,7 +356,7 @@ template= (self.*bounds)()) { - PyErr_SetNone(PyExc_IndexError); + PyErr_Format(PyExc_IndexError, "index %u out of range for %u entries", id, (self.*bounds)()); throw py::error_already_set{}; } @@ -370,7 +370,7 @@ template= (self.*bounds)()) { - PyErr_SetNone(PyExc_IndexError); + PyErr_Format(PyExc_IndexError, "index %u out of range for %u entries", id, (self.*bounds)()); throw py::error_already_set{}; } @@ -384,7 +384,7 @@ template(Trade::AbstractImporter::*f)(UnsignedI } if(id >= (self.*bounds)()) { - PyErr_SetNone(PyExc_IndexError); + PyErr_Format(PyExc_IndexError, "index %u out of range for %u entries", id, (self.*bounds)()); throw py::error_already_set{}; } @@ -399,7 +399,7 @@ template(Trade::AbstractImporter::*f)(UnsignedI return *std::move(out); } /** @todo drop std::string in favor of our own string caster */ -template(Trade::AbstractImporter::*f)(UnsignedInt), Int(Trade::AbstractImporter::*indexForName)(Containers::StringView)> R checkOpenedBoundsResultString(Trade::AbstractImporter& self, const std::string& name) { +template(Trade::AbstractImporter::*f)(UnsignedInt), Int(Trade::AbstractImporter::*indexForName)(Containers::StringView), UnsignedInt(Trade::AbstractImporter::*bounds)() const> R checkOpenedBoundsResultString(Trade::AbstractImporter& self, const std::string& name) { if(!self.isOpened()) { PyErr_SetString(PyExc_AssertionError, "no file opened"); throw py::error_already_set{}; @@ -407,7 +407,9 @@ template(Trade::AbstractImporter::*f)(UnsignedI const Int id = (self.*indexForName)(name); if(id == -1) { - PyErr_SetNone(PyExc_KeyError); + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u entries", name.data(), (self.*bounds)()); throw py::error_already_set{}; } @@ -429,12 +431,13 @@ template(Trade::AbstractImporter::*f)(UnsignedI } if(id >= (self.*bounds)()) { - PyErr_SetString(PyExc_IndexError, "ID out of range"); + PyErr_Format(PyExc_IndexError, "index %u out of range for %u entries", id, (self.*bounds)()); throw py::error_already_set{}; } - if(level >= (self.*levelBounds)(id)) { - PyErr_SetString(PyExc_IndexError, "level out of range"); + const UnsignedInt levelCount = (self.*levelBounds)(id); + if(level >= levelCount) { + PyErr_Format(PyExc_IndexError, "level %u out of range for %u entries", level, levelCount); throw py::error_already_set{}; } @@ -449,7 +452,7 @@ template(Trade::AbstractImporter::*f)(UnsignedI return *std::move(out); } /** @todo drop std::string in favor of our own string caster */ -template(Trade::AbstractImporter::*f)(UnsignedInt, UnsignedInt), Int(Trade::AbstractImporter::*indexForName)(Containers::StringView), UnsignedInt(Trade::AbstractImporter::*levelBounds)(UnsignedInt)> R checkOpenedBoundsResultString(Trade::AbstractImporter& self, const std::string& name, UnsignedInt level) { +template(Trade::AbstractImporter::*f)(UnsignedInt, UnsignedInt), Int(Trade::AbstractImporter::*indexForName)(Containers::StringView), UnsignedInt(Trade::AbstractImporter::*bounds)() const, UnsignedInt(Trade::AbstractImporter::*levelBounds)(UnsignedInt)> R checkOpenedBoundsResultString(Trade::AbstractImporter& self, const std::string& name, UnsignedInt level) { if(!self.isOpened()) { PyErr_SetString(PyExc_AssertionError, "no file opened"); throw py::error_already_set{}; @@ -457,12 +460,15 @@ template(Trade::AbstractImporter::*f)(UnsignedI const Int id = (self.*indexForName)(name); if(id == -1) { - PyErr_SetNone(PyExc_KeyError); + /** @todo may need extra attention when it's no longer a + null-terminated std::string */ + PyErr_Format(PyExc_KeyError, "name %s not found among %u entries", name.data(), (self.*bounds)()); throw py::error_already_set{}; } - if(level >= (self.*levelBounds)(id)) { - PyErr_SetString(PyExc_IndexError, "level out of range"); + const UnsignedInt levelCount = (self.*levelBounds)(id); + if(level >= levelCount) { + PyErr_Format(PyExc_IndexError, "level %u out of range for %u entries", level, levelCount); throw py::error_already_set{}; } @@ -643,7 +649,7 @@ template Containers::PyArrayViewHolder formatStringGetitemSetitem = accessorsForVertexFormat(format); if(!formatStringGetitemSetitem.first()) { - PyErr_SetString(PyExc_NotImplementedError, "access to this vertex format is not implemented yet, sorry"); + PyErr_Format(PyExc_NotImplementedError, "access to %S is not implemented yet, sorry", py::cast(format).ptr()); throw py::error_already_set{}; } return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<1, T>{data.template transposed<0, 1>()[0], formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(mesh)); @@ -840,7 +846,7 @@ template Containers::PyArrayViewHolder formatStringGetitemSetitem = accessorsForSceneFieldType(type); if(!formatStringGetitemSetitem.first()) { - PyErr_SetString(PyExc_NotImplementedError, "access to this scene field type is not implemented yet, sorry"); + PyErr_Format(PyExc_NotImplementedError, "access to %S is not implemented yet, sorry", py::cast(type).ptr()); throw py::error_already_set{}; } return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<1, T>{data.template transposed<0, 1>()[0], formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(scene)); @@ -981,16 +987,21 @@ void trade(py::module_& m) { #endif py::arg("morph_target_id")) .def("attribute_name", [](Trade::MeshData& self, UnsignedInt id) { - if(id >= self.attributeCount()) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return self.attributeName(id); + if(id < self.attributeCount()) + return self.attributeName(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount()); + throw py::error_already_set{}; }, "Attribute name", py::arg("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); + + const UnsignedInt attributeCount = self.attributeCount(name, morphTargetId); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes", id, attributeCount, py::cast(name).ptr()); + else + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes in morph target %i", id, attributeCount, py::cast(name).ptr(), morphTargetId); throw py::error_already_set{}; }, "Absolute ID of a named attribute", py::arg("name"), #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 @@ -998,16 +1009,21 @@ void trade(py::module_& m) { #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); - throw py::error_already_set{}; - } - return self.attributeId(id); + if(id < self.attributeCount()) + return self.attributeId(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount()); + throw py::error_already_set{}; }, "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, Int morphTargetId) { if(const Containers::Optional found = self.findAttributeId(name, id, morphTargetId)) return self.attributeFormat(*found); - PyErr_SetNone(PyExc_KeyError); + + const UnsignedInt attributeCount = self.attributeCount(name, morphTargetId); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes", id, attributeCount, py::cast(name).ptr()); + else + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes in morph target %i", id, attributeCount, py::cast(name).ptr(), morphTargetId); throw py::error_already_set{}; }, "Format of a named attribute", py::arg("name"), #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 @@ -1015,16 +1031,21 @@ void trade(py::module_& m) { #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); - throw py::error_already_set{}; - } - return self.attributeFormat(id); + if(id < self.attributeCount()) + return self.attributeFormat(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount()); + throw py::error_already_set{}; }, "Attribute format", py::arg("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); + + const UnsignedInt attributeCount = self.attributeCount(name, morphTargetId); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes", id, attributeCount, py::cast(name).ptr()); + else + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes in morph target %i", id, attributeCount, py::cast(name).ptr(), morphTargetId); throw py::error_already_set{}; }, "Offset of a named attribute", py::arg("name"), #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 @@ -1032,16 +1053,21 @@ void trade(py::module_& m) { #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); - throw py::error_already_set{}; - } - return self.attributeOffset(id); + if(id < self.attributeCount()) + return self.attributeOffset(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount()); + throw py::error_already_set{}; }, "Attribute offset", py::arg("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); + + const UnsignedInt attributeCount = self.attributeCount(name, morphTargetId); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes", id, attributeCount, py::cast(name).ptr()); + else + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes in morph target %i", id, attributeCount, py::cast(name).ptr(), morphTargetId); throw py::error_already_set{}; }, "Stride of a named attribute", py::arg("name"), #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 @@ -1049,16 +1075,21 @@ void trade(py::module_& m) { #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); - throw py::error_already_set{}; - } - return self.attributeStride(id); + if(id < self.attributeCount()) + return self.attributeStride(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount()); + throw py::error_already_set{}; }, "Attribute stride", py::arg("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); + + const UnsignedInt attributeCount = self.attributeCount(name, morphTargetId); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes", id, attributeCount, py::cast(name).ptr()); + else + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes in morph target %i", id, attributeCount, py::cast(name).ptr(), morphTargetId); throw py::error_already_set{}; }, "Array size of a named attribute", py::arg("name"), #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 @@ -1066,81 +1097,93 @@ void trade(py::module_& m) { #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); - throw py::error_already_set{}; - } - return self.attributeArraySize(id); + if(id < self.attributeCount()) + return self.attributeArraySize(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount()); + throw py::error_already_set{}; }, "Attribute array size", py::arg("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{}; - } - /** @todo handle arrays (return a 2D view, and especially annotate - the return type properly in the docs) */ - if(self.attributeArraySize(*found) != 0) { - PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); - throw py::error_already_set{}; - } - return meshAttributeView(self, *found, self.attribute(*found)); + if(const Containers::Optional found = self.findAttributeId(name, id, morphTargetId)) { + /** @todo handle arrays (return a 2D view, and especially + annotate the return type properly in the docs) */ + if(self.attributeArraySize(*found) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); + throw py::error_already_set{}; + } + return meshAttributeView(self, *found, self.attribute(*found)); + } + + const UnsignedInt attributeCount = self.attributeCount(name, morphTargetId); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes", id, attributeCount, py::cast(name).ptr()); + else + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes in morph target %i", id, attributeCount, py::cast(name).ptr(), morphTargetId); + throw py::error_already_set{}; }, "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); - throw py::error_already_set{}; - } - /** @todo handle arrays (return a 2D view, and especially annotate - the return type properly in the docs) */ - if(self.attributeArraySize(id) != 0) { - PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); - throw py::error_already_set{}; + if(id < self.attributeCount()) { + /** @todo handle arrays (return a 2D view, and especially + annotate the return type properly in the docs) */ + if(self.attributeArraySize(id) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); + throw py::error_already_set{}; + } + return meshAttributeView(self, id, self.attribute(id)); } - return meshAttributeView(self, id, self.attribute(id)); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount()); + throw py::error_already_set{}; }, "Data for given attribute", py::arg("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{}; - } if(!(self.vertexDataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AttributeError, "mesh vertex data is not mutable"); throw py::error_already_set{}; } - /** @todo handle arrays (return a 2D view, and especially annotate - the return type properly in the docs) */ - if(self.attributeArraySize(*found) != 0) { - PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); - throw py::error_already_set{}; + + if(const Containers::Optional found = self.findAttributeId(name, id, morphTargetId)) { + /** @todo handle arrays (return a 2D view, and especially + annotate the return type properly in the docs) */ + if(self.attributeArraySize(*found) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); + throw py::error_already_set{}; + } + return meshAttributeView(self, *found, self.mutableAttribute(*found)); } - return meshAttributeView(self, *found, self.mutableAttribute(*found)); + + const UnsignedInt attributeCount = self.attributeCount(name, morphTargetId); + if(morphTargetId == -1) + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes", id, attributeCount, py::cast(name).ptr()); + else + PyErr_Format(PyExc_KeyError, "index %u out of range for %u %S attributes in morph target %i", id, attributeCount, py::cast(name).ptr(), morphTargetId); + throw py::error_already_set{}; }, "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); - throw py::error_already_set{}; - } if(!(self.vertexDataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AttributeError, "mesh vertex data is not mutable"); throw py::error_already_set{}; } - /** @todo handle arrays (return a 2D view, and especially annotate - the return type properly in the docs) */ - if(self.attributeArraySize(id) != 0) { - PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); - throw py::error_already_set{}; + + if(id < self.attributeCount()) { + /** @todo handle arrays (return a 2D view, and especially + annotate the return type properly in the docs) */ + if(self.attributeArraySize(id) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); + throw py::error_already_set{}; + } + return meshAttributeView(self, id, self.mutableAttribute(id)); } - return meshAttributeView(self, id, self.mutableAttribute(id)); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount()); + throw py::error_already_set{}; }, "Mutable data for given attribute", py::arg("id")) .def_property_readonly("owner", [](Trade::MeshData& self) { @@ -1311,200 +1354,194 @@ void trade(py::module_& m) { overload gets picked even if an enum is passed from Python, causing massive suffering */ .def("field_name", [](Trade::SceneData& self, UnsignedInt id) { - if(id >= self.fieldCount()) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return self.fieldName(id); + if(id < self.fieldCount()) + return self.fieldName(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount()); + throw py::error_already_set{}; }, "Field name", py::arg("id")) .def("field_flags", [](Trade::SceneData& self, Trade::SceneField fieldName) { - const Containers::Optional foundField = self.findFieldId(fieldName); - if(!foundField) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; - } - return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(*foundField))); + if(const Containers::Optional foundField = self.findFieldId(fieldName)) + return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(*foundField))); + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(fieldName).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Flags of a named field", py::arg("name")) .def("field_flags", [](Trade::SceneData& self, UnsignedInt id) { - if(id >= self.fieldCount()) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(id))); + if(id < self.fieldCount()) + return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(id))); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount()); + throw py::error_already_set{}; }, "Field flags", py::arg("id")) .def("field_type", [](Trade::SceneData& self, Trade::SceneField fieldName) { - const Containers::Optional foundField = self.findFieldId(fieldName); - if(!foundField) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; - } - return self.fieldType(*foundField); + if(const Containers::Optional foundField = self.findFieldId(fieldName)) + return self.fieldType(*foundField); + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(fieldName).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Type of a named field", py::arg("name")) .def("field_type", [](Trade::SceneData& self, UnsignedInt id) { - if(id >= self.fieldCount()) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return self.fieldType(id); + if(id < self.fieldCount()) + return self.fieldType(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount()); + throw py::error_already_set{}; }, "Field type", py::arg("id")) .def("field_size", [](Trade::SceneData& self, Trade::SceneField fieldName) { - const Containers::Optional foundField = self.findFieldId(fieldName); - if(!foundField) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; - } - return self.fieldSize(*foundField); + if(const Containers::Optional foundField = self.findFieldId(fieldName)) + return self.fieldSize(*foundField); + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(fieldName).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Number of entries in a named field", py::arg("name")) .def("field_size", [](Trade::SceneData& self, UnsignedInt id) { - if(id >= self.fieldCount()) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return self.fieldSize(id); + if(id < self.fieldCount()) + return self.fieldSize(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount()); + throw py::error_already_set{}; }, "Number of entries in a field", py::arg("id")) .def("field_array_size", [](Trade::SceneData& self, Trade::SceneField fieldName) { - const Containers::Optional foundField = self.findFieldId(fieldName); - if(!foundField) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; - } + if(const Containers::Optional foundField = self.findFieldId(fieldName)) return self.fieldArraySize(*foundField); + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(fieldName).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Array size of a named field", py::arg("name")) .def("field_array_size", [](Trade::SceneData& self, UnsignedInt id) { - if(id >= self.fieldCount()) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return self.fieldArraySize(id); + if(id < self.fieldCount()) + return self.fieldArraySize(id); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount()); + throw py::error_already_set{}; }, "Field array size", py::arg("id")) .def("field_id", [](Trade::SceneData& self, Trade::SceneField name) { if(const Containers::Optional found = self.findFieldId(name)) return *found; - PyErr_SetNone(PyExc_KeyError); + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(name).ptr(), self.fieldCount()); throw py::error_already_set{}; }, "Absolute ID of a named field", py::arg("name")) .def("has_field", &Trade::SceneData::hasField, "Whether the scene has given field") .def("field_object_offset", [](Trade::SceneData& self, Trade::SceneField fieldName, UnsignedLong object, std::size_t offset) { - const Containers::Optional foundField = self.findFieldId(fieldName); - if(!foundField) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; - } - if(object >= self.mappingBound()) { - PyErr_SetString(PyExc_IndexError, "object out of range"); - throw py::error_already_set{}; - } - if(offset >= self.fieldSize(*foundField)) { - PyErr_SetString(PyExc_IndexError, "offset out of range"); - throw py::error_already_set{}; - } - const Containers::Optional found = self.findFieldObjectOffset(*foundField, object, offset); - if(!found) { - PyErr_SetNone(PyExc_LookupError); + if(const Containers::Optional foundField = self.findFieldId(fieldName)) { + if(object >= self.mappingBound()) { + PyErr_Format(PyExc_IndexError, "index %llu out of range for %llu objects", object, self.mappingBound()); + throw py::error_already_set{}; + } + if(offset > self.fieldSize(*foundField)) { + PyErr_Format(PyExc_IndexError, "offset %zu out of range for a field of size %zu", offset, self.fieldSize(*foundField)); + throw py::error_already_set{}; + } + if(const Containers::Optional found = self.findFieldObjectOffset(*foundField, object, offset)) + return *found; + + PyErr_Format(PyExc_LookupError, "object %llu not found in field %S starting at offset %zu", object, py::cast(fieldName).ptr(), offset); throw py::error_already_set{}; } - return *found; + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(fieldName).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Offset of an object in given name field", py::arg("field_name"), py::arg("object"), py::arg("offset") = 0) .def("field_object_offset", [](Trade::SceneData& self, UnsignedInt fieldId, UnsignedLong object, std::size_t offset) { if(fieldId >= self.fieldCount()) { - PyErr_SetString(PyExc_IndexError, "field out of range"); + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", fieldId, self.fieldCount()); throw py::error_already_set{}; } if(object >= self.mappingBound()) { - PyErr_SetString(PyExc_IndexError, "object out of range"); - throw py::error_already_set{}; - } - if(offset >= self.fieldSize(fieldId)) { - PyErr_SetString(PyExc_IndexError, "offset out of range"); + PyErr_Format(PyExc_IndexError, "index %llu out of range for %llu objects", object, self.mappingBound()); throw py::error_already_set{}; } - const Containers::Optional found = self.findFieldObjectOffset(fieldId, object, offset); - if(!found) { - PyErr_SetNone(PyExc_LookupError); + if(offset > self.fieldSize(fieldId)) { + PyErr_Format(PyExc_IndexError, "offset %zu out of range for a field of size %zu", offset, self.fieldSize(fieldId)); throw py::error_already_set{}; } - return *found; + if(const Containers::Optional found = self.findFieldObjectOffset(fieldId, object, offset)) + return *found; + + PyErr_Format(PyExc_LookupError, "object %llu not found in field %S starting at offset %zu", object, py::cast(self.fieldName(fieldId)).ptr(), offset); + throw py::error_already_set{}; }, "Offset of an object in given field", py::arg("field_id"), py::arg("object"), py::arg("offset") = 0) .def("has_field_object", [](Trade::SceneData& self, Trade::SceneField fieldName, UnsignedLong object) { - const Containers::Optional foundField = self.findFieldId(fieldName); - if(!foundField) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; - } - if(object >= self.mappingBound()) { - PyErr_SetString(PyExc_IndexError, "object out of range"); - throw py::error_already_set{}; + if(const Containers::Optional foundField = self.findFieldId(fieldName)) { + if(object >= self.mappingBound()) { + PyErr_Format(PyExc_IndexError, "index %llu out of range for %llu objects", object, self.mappingBound()); + throw py::error_already_set{}; + } + return self.hasFieldObject(*foundField, object); } - return self.hasFieldObject(*foundField, object); + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(fieldName).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Whether a scene field has given object", py::arg("field_name"), py::arg("object")) .def("has_field_object", [](Trade::SceneData& self, UnsignedInt fieldId, UnsignedLong object) { if(fieldId >= self.fieldCount()) { - PyErr_SetString(PyExc_IndexError, "field out of range"); + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", fieldId, self.fieldCount()); throw py::error_already_set{}; } if(object >= self.mappingBound()) { - PyErr_SetString(PyExc_IndexError, "object out of range"); + PyErr_Format(PyExc_IndexError, "index %llu out of range for %llu objects", object, self.mappingBound()); throw py::error_already_set{}; } return self.hasFieldObject(fieldId, object); }, "Whether a scene field has given object", py::arg("field_id"), py::arg("object")) .def("mapping", [](Trade::SceneData& self, Trade::SceneField name) { - const Containers::Optional found = self.findFieldId(name); - if(!found) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; + if(const Containers::Optional found = self.findFieldId(name)) { + return sceneMappingView(self, self.mapping(*found)); } - return sceneMappingView(self, self.mapping(*found)); + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(name).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Object mapping data for given named field", py::arg("name")) .def("mapping", [](Trade::SceneData& self, UnsignedInt id) { - if(id >= self.fieldCount()) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } - return sceneMappingView(self, self.mapping(id)); + if(id < self.fieldCount()) + return sceneMappingView(self, self.mapping(id)); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount()); + throw py::error_already_set{}; }, "Object mapping data for given field", py::arg("name")) .def("mutable_mapping", [](Trade::SceneData& self, Trade::SceneField name) { - const Containers::Optional found = self.findFieldId(name); - if(!found) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; - } if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); throw py::error_already_set{}; } - return sceneMappingView(self, self.mutableMapping(*found)); + if(const Containers::Optional found = self.findFieldId(name)) + return sceneMappingView(self, self.mutableMapping(*found)); + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(name).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Mutable object mapping data for given named field", py::arg("name")) .def("mutable_mapping", [](Trade::SceneData& self, UnsignedInt id) { - if(id >= self.fieldCount()) { - PyErr_SetNone(PyExc_IndexError); - throw py::error_already_set{}; - } if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); throw py::error_already_set{}; } - return sceneMappingView(self, self.mutableMapping(id)); + if(id < self.fieldCount()) + return sceneMappingView(self, self.mutableMapping(id)); + + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount()); + throw py::error_already_set{}; }, "Mutable object mapping data for given field", py::arg("name")) .def("field", [](Trade::SceneData& self, Trade::SceneField name) { - const Containers::Optional found = self.findFieldId(name); - if(!found) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; - } - /** @todo handle arrays (return a 2D (bit) view) */ - if(self.fieldArraySize(*found) != 0) { - PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); - throw py::error_already_set{}; - } - /** @todo annotate the return type properly in the docs */ - if(self.fieldType(*found) == Trade::SceneFieldType::Bit) - return pyCastButNotShitty(Containers::pyArrayViewHolder(self.fieldBits(*found), py::cast(self))); - return pyCastButNotShitty(sceneFieldView(self, *found, self.field(*found))); + if(const Containers::Optional found = self.findFieldId(name)) { + /** @todo handle arrays (return a 2D (bit) view) */ + if(self.fieldArraySize(*found) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); + throw py::error_already_set{}; + } + /** @todo annotate the return type properly in the docs */ + if(self.fieldType(*found) == Trade::SceneFieldType::Bit) + return pyCastButNotShitty(Containers::pyArrayViewHolder(self.fieldBits(*found), py::cast(self))); + return pyCastButNotShitty(sceneFieldView(self, *found, self.field(*found))); + } + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(name).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Data for given named field", py::arg("name")) .def("field", [](Trade::SceneData& self, UnsignedInt id) { if(id >= self.fieldCount()) { - PyErr_SetNone(PyExc_IndexError); + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount()); throw py::error_already_set{}; } /** @todo handle arrays (return a 2D (bit) view) */ @@ -1518,28 +1555,28 @@ void trade(py::module_& m) { return pyCastButNotShitty(sceneFieldView(self, id, self.field(id))); }, "Data for given field", py::arg("name")) .def("mutable_field", [](Trade::SceneData& self, Trade::SceneField name) { - const Containers::Optional found = self.findFieldId(name); - if(!found) { - PyErr_SetNone(PyExc_KeyError); - throw py::error_already_set{}; - } if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); throw py::error_already_set{}; } - /** @todo handle arrays (return a 2D (bit) view) */ - if(self.fieldArraySize(*found) != 0) { - PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); - throw py::error_already_set{}; + if(const Containers::Optional found = self.findFieldId(name)) { + /** @todo handle arrays (return a 2D (bit) view) */ + if(self.fieldArraySize(*found) != 0) { + PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); + throw py::error_already_set{}; + } + /** @todo annotate the return type properly in the docs */ + if(self.fieldType(*found) == Trade::SceneFieldType::Bit) + return pyCastButNotShitty(Containers::pyArrayViewHolder(self.mutableFieldBits(*found), py::cast(self))); + return pyCastButNotShitty(sceneFieldView(self, *found, self.mutableField(*found))); } - /** @todo annotate the return type properly in the docs */ - if(self.fieldType(*found) == Trade::SceneFieldType::Bit) - return pyCastButNotShitty(Containers::pyArrayViewHolder(self.mutableFieldBits(*found), py::cast(self))); - return pyCastButNotShitty(sceneFieldView(self, *found, self.mutableField(*found))); + + PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(name).ptr(), self.fieldCount()); + throw py::error_already_set{}; }, "Mutable data for given named field", py::arg("name")) .def("mutable_field", [](Trade::SceneData& self, UnsignedInt id) { if(id >= self.fieldCount()) { - PyErr_SetNone(PyExc_IndexError); + PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount()); throw py::error_already_set{}; } if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { @@ -1656,7 +1693,7 @@ void trade(py::module_& m) { .def("scene_name", checkOpenedBoundsReturnsString, "Scene name", py::arg("id")) .def("object_name", checkOpenedBoundsReturnsString, "Scene name", py::arg("id")) .def("scene", checkOpenedBoundsResult, "Scene", py::arg("id")) - .def("scene", checkOpenedBoundsResultString, "Scene for given name", py::arg("name")) + .def("scene", checkOpenedBoundsResultString, "Scene for given name", py::arg("name")) /** @todo drop std::string in favor of our own string caster */ .def("scene_field_for_name", [](Trade::AbstractImporter& self, const std::string& name) -> Containers::Optional { const Trade::SceneField field = self.sceneFieldForName(name); @@ -1677,7 +1714,7 @@ void trade(py::module_& m) { .def("mesh_for_name", checkOpenedString, "Mesh ID for given name", py::arg("name")) .def("mesh_name", checkOpenedBoundsReturnsString, "Mesh name", py::arg("id")) .def("mesh", checkOpenedBoundsResult, "Mesh", py::arg("id"), py::arg("level") = 0) - .def("mesh", checkOpenedBoundsResultString, "Mesh for given name", py::arg("name"), py::arg("level") = 0) + .def("mesh", checkOpenedBoundsResultString, "Mesh for given name", py::arg("name"), py::arg("level") = 0) /** @todo drop std::string in favor of our own string caster */ .def("mesh_attribute_for_name", [](Trade::AbstractImporter& self, const std::string& name) -> Containers::Optional { const Trade::MeshAttribute attribute = self.meshAttributeForName(name); @@ -1696,7 +1733,7 @@ void trade(py::module_& m) { .def("texture_for_name", checkOpenedString, "Texture ID for given name", py::arg("name")) .def("texture_name", checkOpenedBoundsReturnsString, "Texture name", py::arg("id")) .def("texture", checkOpenedBoundsResult, "Texture", py::arg("id")) - .def("texture", checkOpenedBoundsResultString, "Texture for given name", py::arg("name")) + .def("texture", checkOpenedBoundsResultString, "Texture for given name", py::arg("name")) .def_property_readonly("image1d_count", checkOpened, "One-dimensional image count") .def_property_readonly("image2d_count", checkOpened, "Two-dimensional image count") @@ -1711,11 +1748,11 @@ void trade(py::module_& m) { .def("image2d_name", checkOpenedBoundsReturnsString, "Two-dimensional image name", py::arg("id")) .def("image3d_name", checkOpenedBoundsReturnsString, "Three-dimensional image name", py::arg("id")) .def("image1d", checkOpenedBoundsResult, "One-dimensional image", py::arg("id"), py::arg("level") = 0) - .def("image1d", checkOpenedBoundsResultString, "One-dimensional image for given name", py::arg("name"), py::arg("level") = 0) + .def("image1d", checkOpenedBoundsResultString, "One-dimensional image for given name", py::arg("name"), py::arg("level") = 0) .def("image2d", checkOpenedBoundsResult, "Two-dimensional image", py::arg("id"), py::arg("level") = 0) - .def("image2d", checkOpenedBoundsResultString, "Two-dimensional image for given name", py::arg("name"), py::arg("level") = 0) + .def("image2d", checkOpenedBoundsResultString, "Two-dimensional image for given name", py::arg("name"), py::arg("level") = 0) .def("image3d", checkOpenedBoundsResult, "Three-dimensional image", py::arg("id"), py::arg("level") = 0) - .def("image3d", checkOpenedBoundsResultString, "Threee-dimensional image for given name", py::arg("name"), py::arg("level") = 0); + .def("image3d", checkOpenedBoundsResultString, "Threee-dimensional image for given name", py::arg("name"), py::arg("level") = 0); py::class_, PluginManager::AbstractManager> importerManager{m, "ImporterManager", "Manager for importer plugins"}; corrade::manager(importerManager);