From 13030328293027726304991baac70a87574518ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 27 Oct 2023 18:26:30 +0200 Subject: [PATCH] python: add actual exception messages to all Trade and *Tools APIs. The time saved on not writing those messages back then was really not worth the time wasted on figuring out what the hell every time one of these fires. And yet, somehow Python itself and a ton of libraries raises just an IndexError alone, with nothing saying *how* wrong it was. Those details, present in Magnum's own C++ asserts, proved to be extremely valuable for being able to quickly figure out what's wrong, often even without even having to look at the code or step through in the debugger. Also fixes an issue where SceneData.field_object_offset() was rejecting offsets right at the end of the field. Wasn't caught by tests because apparently LookupError is a base of IndexError and as there was no message it just failed elsewhere without being noticed. --- src/python/magnum/meshtools.cpp | 52 ++- src/python/magnum/scenetools.cpp | 2 +- src/python/magnum/test/test_meshtools.py | 52 +-- src/python/magnum/test/test_scenetools.py | 2 +- src/python/magnum/test/test_trade.py | 255 +++++------ src/python/magnum/trade.cpp | 509 ++++++++++++---------- 6 files changed, 468 insertions(+), 404 deletions(-) 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);