Browse Source

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.
next
Vladimír Vondruš 3 years ago
parent
commit
1303032829
  1. 52
      src/python/magnum/meshtools.cpp
  2. 2
      src/python/magnum/scenetools.cpp
  3. 52
      src/python/magnum/test/test_meshtools.py
  4. 2
      src/python/magnum/test/test_scenetools.py
  5. 255
      src/python/magnum/test/test_trade.py
  6. 509
      src/python/magnum/trade.cpp

52
src/python/magnum/meshtools.cpp

@ -105,11 +105,11 @@ void meshtools(py::module_& m) {
mesh.primitive() == MeshPrimitive::TriangleStrip || mesh.primitive() == MeshPrimitive::TriangleStrip ||
mesh.primitive() == MeshPrimitive::TriangleFan) 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{}; throw py::error_already_set{};
} }
if(mesh.primitive() != primitive) { 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{}; throw py::error_already_set{};
} }
} }
@ -161,7 +161,7 @@ void meshtools(py::module_& m) {
mesh.primitive() != MeshPrimitive::TriangleStrip && mesh.primitive() != MeshPrimitive::TriangleStrip &&
mesh.primitive() != MeshPrimitive::TriangleFan) 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{}; throw py::error_already_set{};
} }
/** @todo check that the indices aren't impl-specific once it's /** @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) { .def("transform2d", [](const Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id, Int morphTargetId, MeshTools::InterleaveFlag flags) {
const Containers::Optional<UnsignedInt> positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); const Containers::Optional<UnsignedInt> positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId);
if(!positionAttributeId) { 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{}; throw py::error_already_set{};
} }
if(vertexFormatComponentCount(mesh.attributeFormat(*positionAttributeId)) != 2) { 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{}; throw py::error_already_set{};
} }
/** @todo check that the positions aren't impl-specific once /** @todo check that the positions aren't impl-specific once
@ -214,11 +217,14 @@ void meshtools(py::module_& m) {
const Containers::Optional<UnsignedInt> positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); const Containers::Optional<UnsignedInt> positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId);
if(!positionAttributeId) { 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{}; throw py::error_already_set{};
} }
if(mesh.attributeFormat(*positionAttributeId) != VertexFormat::Vector2) { 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{}; 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) { .def("transform3d", [](const Trade::MeshData& mesh, const Matrix4& transformation, UnsignedInt id, Int morphTargetId, MeshTools::InterleaveFlag flags) {
const Containers::Optional<UnsignedInt> positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); const Containers::Optional<UnsignedInt> positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId);
if(!positionAttributeId) { 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{}; throw py::error_already_set{};
} }
if(vertexFormatComponentCount(mesh.attributeFormat(*positionAttributeId)) != 3) { 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{}; throw py::error_already_set{};
} }
/** @todo check that the positions, normals, ... aren't /** @todo check that the positions, normals, ... aren't
@ -255,11 +264,14 @@ void meshtools(py::module_& m) {
const Containers::Optional<UnsignedInt> positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId); const Containers::Optional<UnsignedInt> positionAttributeId = mesh.findAttributeId(Trade::MeshAttribute::Position, id, morphTargetId);
if(!positionAttributeId) { 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{}; throw py::error_already_set{};
} }
if(mesh.attributeFormat(*positionAttributeId) != VertexFormat::Vector3) { 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{}; throw py::error_already_set{};
} }
@ -270,15 +282,15 @@ void meshtools(py::module_& m) {
(mesh.attributeFormat(*tangentAttributeId) != VertexFormat::Vector3 && (mesh.attributeFormat(*tangentAttributeId) != VertexFormat::Vector3 &&
mesh.attributeFormat(*tangentAttributeId) != VertexFormat::Vector4)) 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{}; throw py::error_already_set{};
} }
if(bitangentAttributeId && mesh.attributeFormat(*bitangentAttributeId) != VertexFormat::Vector3) { 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{}; throw py::error_already_set{};
} }
if(normalAttributeId && mesh.attributeFormat(*normalAttributeId) != VertexFormat::Vector3) { 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{}; 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) { .def("transform_texture_coordinates2d", [](const Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id, Int morphTargetId, MeshTools::InterleaveFlag flags) {
const Containers::Optional<UnsignedInt> textureCoordinateAttributeId = mesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates, id, morphTargetId); const Containers::Optional<UnsignedInt> textureCoordinateAttributeId = mesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates, id, morphTargetId);
if(!textureCoordinateAttributeId) { 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{}; throw py::error_already_set{};
} }
/** @todo check that the texture coordinates aren't impl-specific /** @todo check that the texture coordinates aren't impl-specific
@ -311,11 +326,14 @@ void meshtools(py::module_& m) {
const Containers::Optional<UnsignedInt> textureCoordinateAttributeId = mesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates, id, morphTargetId); const Containers::Optional<UnsignedInt> textureCoordinateAttributeId = mesh.findAttributeId(Trade::MeshAttribute::TextureCoordinates, id, morphTargetId);
if(!textureCoordinateAttributeId) { 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{}; throw py::error_already_set{};
} }
if(mesh.attributeFormat(*textureCoordinateAttributeId) != VertexFormat::Vector2) { 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{}; throw py::error_already_set{};
} }

2
src/python/magnum/scenetools.cpp

@ -86,7 +86,7 @@ void scenetools(py::module_& m) {
for(std::size_t i = 0; i != entriesToKeep.size(); ++i) { for(std::size_t i = 0; i != entriesToKeep.size(); ++i) {
const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(entriesToKeep[i].first()); const Containers::Optional<UnsignedInt> fieldId = scene.findFieldId(entriesToKeep[i].first());
if(!fieldId) { 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{}; throw py::error_already_set{};
} }
if(usedFields[*fieldId]) { if(usedFields[*fieldId]) {

52
src/python/magnum/test/test_meshtools.py

@ -61,16 +61,16 @@ class Concatenate(unittest.TestCase):
meshtools.concatenate([]) meshtools.concatenate([])
def test_invalid_primitive(self): 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()]) meshtools.concatenate([primitives.cube_solid(), primitives.plane_solid()])
# Should check that also for the first argument # 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()]) meshtools.concatenate([primitives.plane_solid()])
def test_inconsistent_primitive(self): 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()]) 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()]) meshtools.concatenate([primitives.line3d(), primitives.cube_solid()])
class Duplicate(unittest.TestCase): class Duplicate(unittest.TestCase):
@ -102,7 +102,7 @@ class GenerateIndices(unittest.TestCase):
mesh = primitives.cube_solid() mesh = primitives.cube_solid()
self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) 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) meshtools.generate_indices(mesh)
class Filter(unittest.TestCase): class Filter(unittest.TestCase):
@ -297,44 +297,44 @@ class Transform(unittest.TestCase):
mesh = meshtools.copy(primitives.square_solid(primitives.SquareFlags.TEXTURE_COORDINATES)) mesh = meshtools.copy(primitives.square_solid(primitives.SquareFlags.TEXTURE_COORDINATES))
# ID not found # 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) 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) 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) 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) 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) 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) meshtools.transform_texture_coordinates2d_in_place(mesh, Matrix3(), id=1)
# Morph target not found # 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) 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) 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) 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) 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) 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) meshtools.transform_texture_coordinates2d_in_place(mesh, Matrix3(), morph_target_id=37)
def test_not_2d_not_3d(self): def test_not_2d_not_3d(self):
mesh2d = primitives.line2d() mesh2d = primitives.line2d()
mesh3d = primitives.line3d() 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()) 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()) 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()) 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()) meshtools.transform3d_in_place(mesh2d, Matrix4())
def test_not_float(self): 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)) self.assertEqual(packed_texcoords.attribute(trade.MeshAttribute.TEXTURE_COORDINATES)[1], (-0.5, 0.0))
# TODO test 2D position with something that's actually 2D # 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()) 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()) meshtools.transform3d_in_place(importer.mesh('packed positions'), Matrix4())
# TODO test also with an explicit ID and morph target ID to verify it's # TODO test also with an explicit ID and morph target ID to verify it's
# correctly propagated # 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()) 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()) 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()) meshtools.transform_texture_coordinates2d_in_place(importer.mesh('packed texcoords'), Matrix3())
def test_in_place_not_mutable(self): def test_in_place_not_mutable(self):

2
src/python/magnum/test/test_scenetools.py

@ -228,7 +228,7 @@ class Filter(unittest.TestCase):
scenetools.filter_field_entries(scene, [ scenetools.filter_field_entries(scene, [
(8, containers.BitArrayView()) (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, [ scenetools.filter_field_entries(scene, [
(trade.SceneField.CAMERA, containers.BitArray.value_init(2)), (trade.SceneField.CAMERA, containers.BitArray.value_init(2)),
(trade.SceneField.LIGHT, containers.BitArrayView()) (trade.SceneField.LIGHT, containers.BitArrayView())

255
src/python/magnum/test/test_trade.py

@ -198,7 +198,7 @@ class ImageData(unittest.TestCase):
image = importer.image2d(0) image = importer.image2d(0)
self.assertEqual(image.format, PixelFormat.DEPTH32F_STENCIL8UI) 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 image.pixels
class MeshData(unittest.TestCase): class MeshData(unittest.TestCase):
@ -597,70 +597,75 @@ class MeshData(unittest.TestCase):
mesh = importer.mesh(0) mesh = importer.mesh(0)
# Access by OOB ID # Access by OOB ID. Deprecated build contains additional 2 backwards
with self.assertRaises(IndexError): # 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()) mesh.attribute_name(mesh.attribute_count())
with self.assertRaises(IndexError): with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage):
mesh.attribute_id(mesh.attribute_count()) mesh.attribute_id(mesh.attribute_count())
with self.assertRaises(IndexError): with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage):
mesh.attribute_format(mesh.attribute_count()) mesh.attribute_format(mesh.attribute_count())
with self.assertRaises(IndexError): with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage):
mesh.attribute_offset(mesh.attribute_count()) mesh.attribute_offset(mesh.attribute_count())
with self.assertRaises(IndexError): with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage):
mesh.attribute_stride(mesh.attribute_count()) mesh.attribute_stride(mesh.attribute_count())
with self.assertRaises(IndexError): with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage):
mesh.attribute_array_size(mesh.attribute_count()) mesh.attribute_array_size(mesh.attribute_count())
with self.assertRaises(IndexError): with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage):
mesh.attribute(mesh.attribute_count()) mesh.attribute(mesh.attribute_count())
with self.assertRaises(IndexError): with self.assertRaisesRegex(IndexError, indexOutOfRangeMessage):
mesh.mutable_attribute(mesh.attribute_count()) mesh.mutable_attribute(mesh.attribute_count())
# Access by nonexistent name # 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) 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) 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) 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) 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) 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) 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) mesh.mutable_attribute(trade.MeshAttribute.TANGENT)
# Access by existing name + OOB ID # 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) 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) 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) 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) 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) 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) 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) mesh.mutable_attribute(trade.MeshAttribute.TEXTURE_COORDINATES, id=2)
# Access by existing name + OOB morph target ID # 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) 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) 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) 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) 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) 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) 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) mesh.mutable_attribute(trade.MeshAttribute.TEXTURE_COORDINATES, morph_target_id=37)
def test_attribute_access_array(self): def test_attribute_access_array(self):
@ -688,13 +693,13 @@ class MeshData(unittest.TestCase):
mesh = importer.mesh(0) mesh = importer.mesh(0)
custom_attribute_id = mesh.attribute_id(custom_attribute) 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) 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) 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) 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) mesh.mutable_attribute(custom_attribute)
class SceneData(unittest.TestCase): class SceneData(unittest.TestCase):
@ -1049,79 +1054,79 @@ class SceneData(unittest.TestCase):
scene = importer.scene(0) scene = importer.scene(0)
# Access by OOB field ID # 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) scene.mutable_field(scene.field_count)
# Access by nonexistent field name # 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) 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) scene.mutable_field(trade.SceneField.SCALING)
# OOB object ID # 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 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) 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 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) scene.field_object_offset(trade.SceneField.PARENT, 4)
# Lookup error # 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 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) scene.field_object_offset(trade.SceneField.CAMERA, 1)
# Lookup error due to field offset being at the end # 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 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)) scene.field_object_offset(trade.SceneField.PARENT, 1, scene.field_size(trade.SceneField.PARENT))
# OOB field offset (offset == size is allowed, tested above) # 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 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) scene.field_object_offset(trade.SceneField.PARENT, 1, scene.field_size(trade.SceneField.PARENT) + 1)
def test_field_access_array(self): def test_field_access_array(self):
@ -1138,13 +1143,13 @@ class SceneData(unittest.TestCase):
scene = importer.scene(0) scene = importer.scene(0)
string_field_id = scene.field_id(string_field) 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) 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) 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) 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) scene.mutable_field(string_field)
class TextureData(unittest.TestCase): class TextureData(unittest.TestCase):
@ -1397,50 +1402,54 @@ class Importer(unittest.TestCase):
importer.image3d('') importer.image3d('')
def test_index_oob(self): def test_index_oob(self):
importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') texture_importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'rgb.png')) texture_importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf'))
with self.assertRaises(IndexError): mesh_importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.scene_name(0) mesh_importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
with self.assertRaises(IndexError):
importer.object_name(0) scene_importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
with self.assertRaises(IndexError): scene_importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf'))
importer.scene(0)
with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"):
with self.assertRaises(IndexError): scene_importer.scene_name(3)
importer.mesh_level_count(0) with self.assertRaisesRegex(IndexError, "index 5 out of range for 5 entries"):
with self.assertRaises(IndexError): scene_importer.object_name(5)
importer.mesh_name(0) with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"):
with self.assertRaisesRegex(IndexError, "ID out of range"): scene_importer.scene(3)
importer.mesh(0)
with self.assertRaisesRegex(IndexError, "index 5 out of range for 5 entries"):
with self.assertRaises(IndexError): mesh_importer.mesh_level_count(5)
importer.texture_name(0) with self.assertRaisesRegex(IndexError, "index 5 out of range for 5 entries"):
with self.assertRaises(IndexError): mesh_importer.mesh_name(5)
importer.texture(0) with self.assertRaisesRegex(IndexError, "index 5 out of range for 5 entries"):
mesh_importer.mesh(5)
with self.assertRaises(IndexError):
importer.image1d_level_count(0) with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"):
with self.assertRaises(IndexError): texture_importer.texture_name(3)
importer.image2d_level_count(1) with self.assertRaisesRegex(IndexError, "index 3 out of range for 3 entries"):
with self.assertRaises(IndexError): texture_importer.texture(3)
importer.image3d_level_count(0)
with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"):
with self.assertRaises(IndexError): texture_importer.image1d_level_count(0)
importer.image1d_name(0) with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 entries"):
with self.assertRaises(IndexError): texture_importer.image2d_level_count(2)
importer.image2d_name(1) with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"):
with self.assertRaises(IndexError): texture_importer.image3d_level_count(0)
importer.image3d_name(0)
with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"):
with self.assertRaisesRegex(IndexError, "ID out of range"): texture_importer.image1d_name(0)
importer.image1d(0) with self.assertRaisesRegex(IndexError, "index 2 out of range for 2 entries"):
with self.assertRaisesRegex(IndexError, "level out of range"): texture_importer.image2d_name(2)
importer.image2d(0, 1) with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"):
with self.assertRaisesRegex(IndexError, "ID out of range"): texture_importer.image3d_name(0)
importer.image2d(1)
with self.assertRaisesRegex(IndexError, "ID out of range"): with self.assertRaisesRegex(IndexError, "index 0 out of range for 0 entries"):
importer.image3d(0) 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): def test_open_failed(self):
importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') importer = trade.ImporterManager().load_and_instantiate('StbImageImporter')
@ -1493,7 +1502,7 @@ class Importer(unittest.TestCase):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'scene.gltf')) 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') importer.scene('Nonexistent')
def test_scene_failed(self): def test_scene_failed(self):
@ -1545,7 +1554,7 @@ class Importer(unittest.TestCase):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) 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) importer.mesh(0, 1)
def test_mesh_by_name(self): def test_mesh_by_name(self):
@ -1559,14 +1568,14 @@ class Importer(unittest.TestCase):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) 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') importer.mesh('Nonexistent')
def test_mesh_by_name_level_oob(self): def test_mesh_by_name_level_oob(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf')) 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) importer.mesh('Non-indexed mesh', 1)
def test_mesh_failed(self): def test_mesh_failed(self):
@ -1589,7 +1598,7 @@ class Importer(unittest.TestCase):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) 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') importer.texture('Nonexistent')
def test_texture_failed(self): def test_texture_failed(self):
@ -1624,12 +1633,12 @@ class Importer(unittest.TestCase):
del importer del importer
self.assertEqual(sys.getrefcount(manager), manager_refcount) 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 refcounting tested in image2d
importer = trade.ImporterManager().load_and_instantiate('StbImageImporter') importer = trade.ImporterManager().load_and_instantiate('StbImageImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'rgb.png')) 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) importer.image2d(0, 1)
def test_image2d_by_name(self): def test_image2d_by_name(self):
@ -1643,7 +1652,7 @@ class Importer(unittest.TestCase):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter') importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'texture.gltf')) 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') importer.image2d('Nonexistent')
def test_image2d_data(self): def test_image2d_data(self):

509
src/python/magnum/trade.cpp

@ -198,7 +198,7 @@ template<UnsignedInt dimensions, class T> Containers::PyArrayViewHolder<Containe
const std::size_t itemsize = pixelFormatSize(format); const std::size_t itemsize = pixelFormatSize(format);
const Containers::Triple<const char*, py::object(*)(const char*), void(*)(char*, py::handle)> formatStringGetitemSetitem = accessorsForPixelFormat(format); const Containers::Triple<const char*, py::object(*)(const char*), void(*)(char*, py::handle)> formatStringGetitemSetitem = accessorsForPixelFormat(format);
if(!formatStringGetitemSetitem.first()) { 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{}; throw py::error_already_set{};
} }
return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<dimensions, T>{flattenPixelView(data, pixels), formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(image)); return Containers::pyArrayViewHolder(Containers::PyStridedArrayView<dimensions, T>{flattenPixelView(data, pixels), formatStringGetitemSetitem.first(), itemsize, formatStringGetitemSetitem.second(), formatStringGetitemSetitem.third()}, py::cast(image));
@ -356,7 +356,7 @@ template<class R, R(Trade::AbstractImporter::*f)(UnsignedInt), UnsignedInt(Trade
} }
if(id >= (self.*bounds)()) { 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{}; throw py::error_already_set{};
} }
@ -370,7 +370,7 @@ template<class R, Containers::String(Trade::AbstractImporter::*f)(R), R(Trade::A
} }
if(id >= (self.*bounds)()) { 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{}; throw py::error_already_set{};
} }
@ -384,7 +384,7 @@ template<class R, Containers::Optional<R>(Trade::AbstractImporter::*f)(UnsignedI
} }
if(id >= (self.*bounds)()) { 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{}; throw py::error_already_set{};
} }
@ -399,7 +399,7 @@ template<class R, Containers::Optional<R>(Trade::AbstractImporter::*f)(UnsignedI
return *std::move(out); return *std::move(out);
} }
/** @todo drop std::string in favor of our own string caster */ /** @todo drop std::string in favor of our own string caster */
template<class R, Containers::Optional<R>(Trade::AbstractImporter::*f)(UnsignedInt), Int(Trade::AbstractImporter::*indexForName)(Containers::StringView)> R checkOpenedBoundsResultString(Trade::AbstractImporter& self, const std::string& name) { template<class R, Containers::Optional<R>(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()) { if(!self.isOpened()) {
PyErr_SetString(PyExc_AssertionError, "no file opened"); PyErr_SetString(PyExc_AssertionError, "no file opened");
throw py::error_already_set{}; throw py::error_already_set{};
@ -407,7 +407,9 @@ template<class R, Containers::Optional<R>(Trade::AbstractImporter::*f)(UnsignedI
const Int id = (self.*indexForName)(name); const Int id = (self.*indexForName)(name);
if(id == -1) { 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{}; throw py::error_already_set{};
} }
@ -429,12 +431,13 @@ template<class R, Containers::Optional<R>(Trade::AbstractImporter::*f)(UnsignedI
} }
if(id >= (self.*bounds)()) { 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{}; throw py::error_already_set{};
} }
if(level >= (self.*levelBounds)(id)) { const UnsignedInt levelCount = (self.*levelBounds)(id);
PyErr_SetString(PyExc_IndexError, "level out of range"); if(level >= levelCount) {
PyErr_Format(PyExc_IndexError, "level %u out of range for %u entries", level, levelCount);
throw py::error_already_set{}; throw py::error_already_set{};
} }
@ -449,7 +452,7 @@ template<class R, Containers::Optional<R>(Trade::AbstractImporter::*f)(UnsignedI
return *std::move(out); return *std::move(out);
} }
/** @todo drop std::string in favor of our own string caster */ /** @todo drop std::string in favor of our own string caster */
template<class R, Containers::Optional<R>(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<class R, Containers::Optional<R>(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()) { if(!self.isOpened()) {
PyErr_SetString(PyExc_AssertionError, "no file opened"); PyErr_SetString(PyExc_AssertionError, "no file opened");
throw py::error_already_set{}; throw py::error_already_set{};
@ -457,12 +460,15 @@ template<class R, Containers::Optional<R>(Trade::AbstractImporter::*f)(UnsignedI
const Int id = (self.*indexForName)(name); const Int id = (self.*indexForName)(name);
if(id == -1) { 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{}; throw py::error_already_set{};
} }
if(level >= (self.*levelBounds)(id)) { const UnsignedInt levelCount = (self.*levelBounds)(id);
PyErr_SetString(PyExc_IndexError, "level out of range"); if(level >= levelCount) {
PyErr_Format(PyExc_IndexError, "level %u out of range for %u entries", level, levelCount);
throw py::error_already_set{}; throw py::error_already_set{};
} }
@ -643,7 +649,7 @@ template<class T> Containers::PyArrayViewHolder<Containers::PyStridedArrayView<1
const std::size_t itemsize = vertexFormatSize(format); const std::size_t itemsize = vertexFormatSize(format);
const Containers::Triple<const char*, py::object(*)(const char*), void(*)(char*, py::handle)> formatStringGetitemSetitem = accessorsForVertexFormat(format); const Containers::Triple<const char*, py::object(*)(const char*), void(*)(char*, py::handle)> formatStringGetitemSetitem = accessorsForVertexFormat(format);
if(!formatStringGetitemSetitem.first()) { 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{}; 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)); 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<class T> Containers::PyArrayViewHolder<Containers::PyStridedArrayView<1
const std::size_t itemsize = Trade::sceneFieldTypeSize(type); const std::size_t itemsize = Trade::sceneFieldTypeSize(type);
const Containers::Triple<const char*, py::object(*)(const char*), void(*)(char*, py::handle)> formatStringGetitemSetitem = accessorsForSceneFieldType(type); const Containers::Triple<const char*, py::object(*)(const char*), void(*)(char*, py::handle)> formatStringGetitemSetitem = accessorsForSceneFieldType(type);
if(!formatStringGetitemSetitem.first()) { 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{}; 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)); 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 #endif
py::arg("morph_target_id")) py::arg("morph_target_id"))
.def("attribute_name", [](Trade::MeshData& self, UnsignedInt id) { .def("attribute_name", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) { if(id < self.attributeCount())
PyErr_SetNone(PyExc_IndexError); return self.attributeName(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount());
return self.attributeName(id); throw py::error_already_set{};
}, "Attribute name", py::arg("id")) }, "Attribute name", py::arg("id"))
.def("attribute_id", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { .def("attribute_id", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId)) if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId))
return *found; 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{}; throw py::error_already_set{};
}, "Absolute ID of a named attribute", py::arg("name"), }, "Absolute ID of a named attribute", py::arg("name"),
#if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206
@ -998,16 +1009,21 @@ void trade(py::module_& m) {
#endif #endif
py::arg("id") = 0, py::arg("morph_target_id") = -1) py::arg("id") = 0, py::arg("morph_target_id") = -1)
.def("attribute_id", [](Trade::MeshData& self, UnsignedInt id) { .def("attribute_id", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) { if(id < self.attributeCount())
PyErr_SetNone(PyExc_IndexError); return self.attributeId(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount());
return self.attributeId(id); throw py::error_already_set{};
}, "Attribute ID in a set of attributes of the same name", py::arg("id")) }, "Attribute ID in a set of attributes of the same name", py::arg("id"))
.def("attribute_format", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { .def("attribute_format", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId)) if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId))
return self.attributeFormat(*found); 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{}; throw py::error_already_set{};
}, "Format of a named attribute", py::arg("name"), }, "Format of a named attribute", py::arg("name"),
#if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206
@ -1015,16 +1031,21 @@ void trade(py::module_& m) {
#endif #endif
py::arg("id") = 0, py::arg("morph_target_id") = -1) py::arg("id") = 0, py::arg("morph_target_id") = -1)
.def("attribute_format", [](Trade::MeshData& self, UnsignedInt id) { .def("attribute_format", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) { if(id < self.attributeCount())
PyErr_SetNone(PyExc_IndexError); return self.attributeFormat(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount());
return self.attributeFormat(id); throw py::error_already_set{};
}, "Attribute format", py::arg("id")) }, "Attribute format", py::arg("id"))
.def("attribute_offset", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { .def("attribute_offset", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId)) if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId))
return self.attributeOffset(*found); 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{}; throw py::error_already_set{};
}, "Offset of a named attribute", py::arg("name"), }, "Offset of a named attribute", py::arg("name"),
#if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206
@ -1032,16 +1053,21 @@ void trade(py::module_& m) {
#endif #endif
py::arg("id") = 0, py::arg("morph_target_id") = -1) py::arg("id") = 0, py::arg("morph_target_id") = -1)
.def("attribute_offset", [](Trade::MeshData& self, UnsignedInt id) { .def("attribute_offset", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) { if(id < self.attributeCount())
PyErr_SetNone(PyExc_IndexError); return self.attributeOffset(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount());
return self.attributeOffset(id); throw py::error_already_set{};
}, "Attribute offset", py::arg("id")) }, "Attribute offset", py::arg("id"))
.def("attribute_stride", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { .def("attribute_stride", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId)) if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId))
return self.attributeStride(*found); 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{}; throw py::error_already_set{};
}, "Stride of a named attribute", py::arg("name"), }, "Stride of a named attribute", py::arg("name"),
#if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206
@ -1049,16 +1075,21 @@ void trade(py::module_& m) {
#endif #endif
py::arg("id") = 0, py::arg("morph_target_id") = -1) py::arg("id") = 0, py::arg("morph_target_id") = -1)
.def("attribute_stride", [](Trade::MeshData& self, UnsignedInt id) { .def("attribute_stride", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) { if(id < self.attributeCount())
PyErr_SetNone(PyExc_IndexError); return self.attributeStride(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount());
return self.attributeStride(id); throw py::error_already_set{};
}, "Attribute stride", py::arg("id")) }, "Attribute stride", py::arg("id"))
.def("attribute_array_size", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { .def("attribute_array_size", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId)) if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId))
return self.attributeArraySize(*found); 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{}; throw py::error_already_set{};
}, "Array size of a named attribute", py::arg("name"), }, "Array size of a named attribute", py::arg("name"),
#if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206
@ -1066,81 +1097,93 @@ void trade(py::module_& m) {
#endif #endif
py::arg("id") = 0, py::arg("morph_target_id") = -1) py::arg("id") = 0, py::arg("morph_target_id") = -1)
.def("attribute_array_size", [](Trade::MeshData& self, UnsignedInt id) { .def("attribute_array_size", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) { if(id < self.attributeCount())
PyErr_SetNone(PyExc_IndexError); return self.attributeArraySize(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u attributes", id, self.attributeCount());
return self.attributeArraySize(id); throw py::error_already_set{};
}, "Attribute array size", py::arg("id")) }, "Attribute array size", py::arg("id"))
.def("attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { .def("attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) {
const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId); if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId)) {
if(!found) { /** @todo handle arrays (return a 2D view, and especially
PyErr_SetNone(PyExc_KeyError); annotate the return type properly in the docs) */
throw py::error_already_set{}; if(self.attributeArraySize(*found) != 0) {
} PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry");
/** @todo handle arrays (return a 2D view, and especially annotate throw py::error_already_set{};
the return type properly in the docs) */ }
if(self.attributeArraySize(*found) != 0) { return meshAttributeView(self, *found, self.attribute(*found));
PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); }
throw py::error_already_set{};
} const UnsignedInt attributeCount = self.attributeCount(name, morphTargetId);
return meshAttributeView(self, *found, self.attribute(*found)); 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"), }, "Data for given named attribute", py::arg("name"),
#if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206
py::kw_only{}, /* new in pybind11 2.6 */ py::kw_only{}, /* new in pybind11 2.6 */
#endif #endif
py::arg("id") = 0, py::arg("morph_target_id") = -1) py::arg("id") = 0, py::arg("morph_target_id") = -1)
.def("attribute", [](Trade::MeshData& self, UnsignedInt id) { .def("attribute", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) { if(id < self.attributeCount()) {
PyErr_SetNone(PyExc_IndexError); /** @todo handle arrays (return a 2D view, and especially
throw py::error_already_set{}; annotate the return type properly in the docs) */
} if(self.attributeArraySize(id) != 0) {
/** @todo handle arrays (return a 2D view, and especially annotate PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry");
the return type properly in the docs) */ throw py::error_already_set{};
if(self.attributeArraySize(id) != 0) { }
PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); return meshAttributeView(self, id, self.attribute(id));
throw py::error_already_set{};
} }
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")) }, "Data for given attribute", py::arg("id"))
.def("mutable_attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) { .def("mutable_attribute", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id, Int morphTargetId) {
const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId);
if(!found) {
PyErr_SetNone(PyExc_KeyError);
throw py::error_already_set{};
}
if(!(self.vertexDataFlags() & Trade::DataFlag::Mutable)) { if(!(self.vertexDataFlags() & Trade::DataFlag::Mutable)) {
PyErr_SetString(PyExc_AttributeError, "mesh vertex data is not mutable"); PyErr_SetString(PyExc_AttributeError, "mesh vertex data is not mutable");
throw py::error_already_set{}; throw py::error_already_set{};
} }
/** @todo handle arrays (return a 2D view, and especially annotate
the return type properly in the docs) */ if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id, morphTargetId)) {
if(self.attributeArraySize(*found) != 0) { /** @todo handle arrays (return a 2D view, and especially
PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); annotate the return type properly in the docs) */
throw py::error_already_set{}; 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"), }, "Mutable data for given named attribute", py::arg("name"),
#if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206 #if PYBIND11_VERSION_MAJOR*100 + PYBIND11_VERSION_MINOR >= 206
py::kw_only{}, /* new in pybind11 2.6 */ py::kw_only{}, /* new in pybind11 2.6 */
#endif #endif
py::arg("id") = 0, py::arg("morph_target_id") = -1) py::arg("id") = 0, py::arg("morph_target_id") = -1)
.def("mutable_attribute", [](Trade::MeshData& self, UnsignedInt id) { .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)) { if(!(self.vertexDataFlags() & Trade::DataFlag::Mutable)) {
PyErr_SetString(PyExc_AttributeError, "mesh vertex data is not mutable"); PyErr_SetString(PyExc_AttributeError, "mesh vertex data is not mutable");
throw py::error_already_set{}; throw py::error_already_set{};
} }
/** @todo handle arrays (return a 2D view, and especially annotate
the return type properly in the docs) */ if(id < self.attributeCount()) {
if(self.attributeArraySize(id) != 0) { /** @todo handle arrays (return a 2D view, and especially
PyErr_SetString(PyExc_NotImplementedError, "array attributes not implemented yet, sorry"); annotate the return type properly in the docs) */
throw py::error_already_set{}; 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")) }, "Mutable data for given attribute", py::arg("id"))
.def_property_readonly("owner", [](Trade::MeshData& self) { .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 overload gets picked even if an enum is passed from Python, causing
massive suffering */ massive suffering */
.def("field_name", [](Trade::SceneData& self, UnsignedInt id) { .def("field_name", [](Trade::SceneData& self, UnsignedInt id) {
if(id >= self.fieldCount()) { if(id < self.fieldCount())
PyErr_SetNone(PyExc_IndexError); return self.fieldName(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount());
return self.fieldName(id); throw py::error_already_set{};
}, "Field name", py::arg("id")) }, "Field name", py::arg("id"))
.def("field_flags", [](Trade::SceneData& self, Trade::SceneField fieldName) { .def("field_flags", [](Trade::SceneData& self, Trade::SceneField fieldName) {
const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName); if(const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName))
if(!foundField) { return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(*foundField)));
PyErr_SetNone(PyExc_KeyError);
throw py::error_already_set{}; PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(fieldName).ptr(), self.fieldCount());
} throw py::error_already_set{};
return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(*foundField)));
}, "Flags of a named field", py::arg("name")) }, "Flags of a named field", py::arg("name"))
.def("field_flags", [](Trade::SceneData& self, UnsignedInt id) { .def("field_flags", [](Trade::SceneData& self, UnsignedInt id) {
if(id >= self.fieldCount()) { if(id < self.fieldCount())
PyErr_SetNone(PyExc_IndexError); return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(id)));
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount());
return Trade::SceneFieldFlag(Containers::enumCastUnderlyingType(self.fieldFlags(id))); throw py::error_already_set{};
}, "Field flags", py::arg("id")) }, "Field flags", py::arg("id"))
.def("field_type", [](Trade::SceneData& self, Trade::SceneField fieldName) { .def("field_type", [](Trade::SceneData& self, Trade::SceneField fieldName) {
const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName); if(const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName))
if(!foundField) { return self.fieldType(*foundField);
PyErr_SetNone(PyExc_KeyError);
throw py::error_already_set{}; PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(fieldName).ptr(), self.fieldCount());
} throw py::error_already_set{};
return self.fieldType(*foundField);
}, "Type of a named field", py::arg("name")) }, "Type of a named field", py::arg("name"))
.def("field_type", [](Trade::SceneData& self, UnsignedInt id) { .def("field_type", [](Trade::SceneData& self, UnsignedInt id) {
if(id >= self.fieldCount()) { if(id < self.fieldCount())
PyErr_SetNone(PyExc_IndexError); return self.fieldType(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount());
return self.fieldType(id); throw py::error_already_set{};
}, "Field type", py::arg("id")) }, "Field type", py::arg("id"))
.def("field_size", [](Trade::SceneData& self, Trade::SceneField fieldName) { .def("field_size", [](Trade::SceneData& self, Trade::SceneField fieldName) {
const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName); if(const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName))
if(!foundField) { return self.fieldSize(*foundField);
PyErr_SetNone(PyExc_KeyError);
throw py::error_already_set{}; PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(fieldName).ptr(), self.fieldCount());
} throw py::error_already_set{};
return self.fieldSize(*foundField);
}, "Number of entries in a named field", py::arg("name")) }, "Number of entries in a named field", py::arg("name"))
.def("field_size", [](Trade::SceneData& self, UnsignedInt id) { .def("field_size", [](Trade::SceneData& self, UnsignedInt id) {
if(id >= self.fieldCount()) { if(id < self.fieldCount())
PyErr_SetNone(PyExc_IndexError); return self.fieldSize(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount());
return self.fieldSize(id); throw py::error_already_set{};
}, "Number of entries in a field", py::arg("id")) }, "Number of entries in a field", py::arg("id"))
.def("field_array_size", [](Trade::SceneData& self, Trade::SceneField fieldName) { .def("field_array_size", [](Trade::SceneData& self, Trade::SceneField fieldName) {
const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName); if(const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName))
if(!foundField) {
PyErr_SetNone(PyExc_KeyError);
throw py::error_already_set{};
}
return self.fieldArraySize(*foundField); 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")) }, "Array size of a named field", py::arg("name"))
.def("field_array_size", [](Trade::SceneData& self, UnsignedInt id) { .def("field_array_size", [](Trade::SceneData& self, UnsignedInt id) {
if(id >= self.fieldCount()) { if(id < self.fieldCount())
PyErr_SetNone(PyExc_IndexError); return self.fieldArraySize(id);
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount());
return self.fieldArraySize(id); throw py::error_already_set{};
}, "Field array size", py::arg("id")) }, "Field array size", py::arg("id"))
.def("field_id", [](Trade::SceneData& self, Trade::SceneField name) { .def("field_id", [](Trade::SceneData& self, Trade::SceneField name) {
if(const Containers::Optional<UnsignedInt> found = self.findFieldId(name)) if(const Containers::Optional<UnsignedInt> found = self.findFieldId(name))
return *found; 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{}; throw py::error_already_set{};
}, "Absolute ID of a named field", py::arg("name")) }, "Absolute ID of a named field", py::arg("name"))
.def("has_field", &Trade::SceneData::hasField, "Whether the scene has given field") .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) { .def("field_object_offset", [](Trade::SceneData& self, Trade::SceneField fieldName, UnsignedLong object, std::size_t offset) {
const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName); if(const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName)) {
if(!foundField) { if(object >= self.mappingBound()) {
PyErr_SetNone(PyExc_KeyError); PyErr_Format(PyExc_IndexError, "index %llu out of range for %llu objects", object, self.mappingBound());
throw py::error_already_set{}; throw py::error_already_set{};
} }
if(object >= self.mappingBound()) { if(offset > self.fieldSize(*foundField)) {
PyErr_SetString(PyExc_IndexError, "object out of range"); PyErr_Format(PyExc_IndexError, "offset %zu out of range for a field of size %zu", offset, self.fieldSize(*foundField));
throw py::error_already_set{}; throw py::error_already_set{};
} }
if(offset >= self.fieldSize(*foundField)) { if(const Containers::Optional<std::size_t> found = self.findFieldObjectOffset(*foundField, object, offset))
PyErr_SetString(PyExc_IndexError, "offset out of range"); return *found;
throw py::error_already_set{};
} PyErr_Format(PyExc_LookupError, "object %llu not found in field %S starting at offset %zu", object, py::cast(fieldName).ptr(), offset);
const Containers::Optional<std::size_t> found = self.findFieldObjectOffset(*foundField, object, offset);
if(!found) {
PyErr_SetNone(PyExc_LookupError);
throw py::error_already_set{}; 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) }, "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) { .def("field_object_offset", [](Trade::SceneData& self, UnsignedInt fieldId, UnsignedLong object, std::size_t offset) {
if(fieldId >= self.fieldCount()) { 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{}; throw py::error_already_set{};
} }
if(object >= self.mappingBound()) { 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{};
}
if(offset >= self.fieldSize(fieldId)) {
PyErr_SetString(PyExc_IndexError, "offset out of range");
throw py::error_already_set{}; throw py::error_already_set{};
} }
const Containers::Optional<std::size_t> found = self.findFieldObjectOffset(fieldId, object, offset); if(offset > self.fieldSize(fieldId)) {
if(!found) { PyErr_Format(PyExc_IndexError, "offset %zu out of range for a field of size %zu", offset, self.fieldSize(fieldId));
PyErr_SetNone(PyExc_LookupError);
throw py::error_already_set{}; throw py::error_already_set{};
} }
return *found; if(const Containers::Optional<std::size_t> 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) }, "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) { .def("has_field_object", [](Trade::SceneData& self, Trade::SceneField fieldName, UnsignedLong object) {
const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName); if(const Containers::Optional<UnsignedInt> foundField = self.findFieldId(fieldName)) {
if(!foundField) { if(object >= self.mappingBound()) {
PyErr_SetNone(PyExc_KeyError); PyErr_Format(PyExc_IndexError, "index %llu out of range for %llu objects", object, self.mappingBound());
throw py::error_already_set{}; throw py::error_already_set{};
} }
if(object >= self.mappingBound()) { return self.hasFieldObject(*foundField, object);
PyErr_SetString(PyExc_IndexError, "object out of range");
throw py::error_already_set{};
} }
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")) }, "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) { .def("has_field_object", [](Trade::SceneData& self, UnsignedInt fieldId, UnsignedLong object) {
if(fieldId >= self.fieldCount()) { 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{}; throw py::error_already_set{};
} }
if(object >= self.mappingBound()) { 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{}; throw py::error_already_set{};
} }
return self.hasFieldObject(fieldId, object); return self.hasFieldObject(fieldId, object);
}, "Whether a scene field has given object", py::arg("field_id"), py::arg("object")) }, "Whether a scene field has given object", py::arg("field_id"), py::arg("object"))
.def("mapping", [](Trade::SceneData& self, Trade::SceneField name) { .def("mapping", [](Trade::SceneData& self, Trade::SceneField name) {
const Containers::Optional<UnsignedInt> found = self.findFieldId(name); if(const Containers::Optional<UnsignedInt> found = self.findFieldId(name)) {
if(!found) { return sceneMappingView(self, self.mapping(*found));
PyErr_SetNone(PyExc_KeyError);
throw py::error_already_set{};
} }
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")) }, "Object mapping data for given named field", py::arg("name"))
.def("mapping", [](Trade::SceneData& self, UnsignedInt id) { .def("mapping", [](Trade::SceneData& self, UnsignedInt id) {
if(id >= self.fieldCount()) { if(id < self.fieldCount())
PyErr_SetNone(PyExc_IndexError); return sceneMappingView(self, self.mapping(id));
throw py::error_already_set{};
} PyErr_Format(PyExc_IndexError, "index %u out of range for %u fields", id, self.fieldCount());
return sceneMappingView(self, self.mapping(id)); throw py::error_already_set{};
}, "Object mapping data for given field", py::arg("name")) }, "Object mapping data for given field", py::arg("name"))
.def("mutable_mapping", [](Trade::SceneData& self, Trade::SceneField name) { .def("mutable_mapping", [](Trade::SceneData& self, Trade::SceneField name) {
const Containers::Optional<UnsignedInt> found = self.findFieldId(name);
if(!found) {
PyErr_SetNone(PyExc_KeyError);
throw py::error_already_set{};
}
if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { if(!(self.dataFlags() & Trade::DataFlag::Mutable)) {
PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); PyErr_SetString(PyExc_AttributeError, "scene data is not mutable");
throw py::error_already_set{}; throw py::error_already_set{};
} }
return sceneMappingView(self, self.mutableMapping(*found)); if(const Containers::Optional<UnsignedInt> 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")) }, "Mutable object mapping data for given named field", py::arg("name"))
.def("mutable_mapping", [](Trade::SceneData& self, UnsignedInt id) { .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)) { if(!(self.dataFlags() & Trade::DataFlag::Mutable)) {
PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); PyErr_SetString(PyExc_AttributeError, "scene data is not mutable");
throw py::error_already_set{}; 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")) }, "Mutable object mapping data for given field", py::arg("name"))
.def("field", [](Trade::SceneData& self, Trade::SceneField name) { .def("field", [](Trade::SceneData& self, Trade::SceneField name) {
const Containers::Optional<UnsignedInt> found = self.findFieldId(name); if(const Containers::Optional<UnsignedInt> found = self.findFieldId(name)) {
if(!found) { /** @todo handle arrays (return a 2D (bit) view) */
PyErr_SetNone(PyExc_KeyError); if(self.fieldArraySize(*found) != 0) {
throw py::error_already_set{}; PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry");
} throw py::error_already_set{};
/** @todo handle arrays (return a 2D (bit) view) */ }
if(self.fieldArraySize(*found) != 0) { /** @todo annotate the return type properly in the docs */
PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); if(self.fieldType(*found) == Trade::SceneFieldType::Bit)
throw py::error_already_set{}; return pyCastButNotShitty(Containers::pyArrayViewHolder(self.fieldBits(*found), py::cast(self)));
} return pyCastButNotShitty(sceneFieldView(self, *found, self.field(*found)));
/** @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))); PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(name).ptr(), self.fieldCount());
return pyCastButNotShitty(sceneFieldView(self, *found, self.field(*found))); throw py::error_already_set{};
}, "Data for given named field", py::arg("name")) }, "Data for given named field", py::arg("name"))
.def("field", [](Trade::SceneData& self, UnsignedInt id) { .def("field", [](Trade::SceneData& self, UnsignedInt id) {
if(id >= self.fieldCount()) { 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{}; throw py::error_already_set{};
} }
/** @todo handle arrays (return a 2D (bit) view) */ /** @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))); return pyCastButNotShitty(sceneFieldView(self, id, self.field(id)));
}, "Data for given field", py::arg("name")) }, "Data for given field", py::arg("name"))
.def("mutable_field", [](Trade::SceneData& self, Trade::SceneField name) { .def("mutable_field", [](Trade::SceneData& self, Trade::SceneField name) {
const Containers::Optional<UnsignedInt> found = self.findFieldId(name);
if(!found) {
PyErr_SetNone(PyExc_KeyError);
throw py::error_already_set{};
}
if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { if(!(self.dataFlags() & Trade::DataFlag::Mutable)) {
PyErr_SetString(PyExc_AttributeError, "scene data is not mutable"); PyErr_SetString(PyExc_AttributeError, "scene data is not mutable");
throw py::error_already_set{}; throw py::error_already_set{};
} }
/** @todo handle arrays (return a 2D (bit) view) */ if(const Containers::Optional<UnsignedInt> found = self.findFieldId(name)) {
if(self.fieldArraySize(*found) != 0) { /** @todo handle arrays (return a 2D (bit) view) */
PyErr_SetString(PyExc_NotImplementedError, "array fields not implemented yet, sorry"); if(self.fieldArraySize(*found) != 0) {
throw py::error_already_set{}; 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) PyErr_Format(PyExc_KeyError, "%S not found among %u fields", py::cast(name).ptr(), self.fieldCount());
return pyCastButNotShitty(Containers::pyArrayViewHolder(self.mutableFieldBits(*found), py::cast(self))); throw py::error_already_set{};
return pyCastButNotShitty(sceneFieldView(self, *found, self.mutableField(*found)));
}, "Mutable data for given named field", py::arg("name")) }, "Mutable data for given named field", py::arg("name"))
.def("mutable_field", [](Trade::SceneData& self, UnsignedInt id) { .def("mutable_field", [](Trade::SceneData& self, UnsignedInt id) {
if(id >= self.fieldCount()) { 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{}; throw py::error_already_set{};
} }
if(!(self.dataFlags() & Trade::DataFlag::Mutable)) { if(!(self.dataFlags() & Trade::DataFlag::Mutable)) {
@ -1656,7 +1693,7 @@ void trade(py::module_& m) {
.def("scene_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::sceneName, &Trade::AbstractImporter::sceneCount>, "Scene name", py::arg("id")) .def("scene_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::sceneName, &Trade::AbstractImporter::sceneCount>, "Scene name", py::arg("id"))
.def("object_name", checkOpenedBoundsReturnsString<UnsignedLong, &Trade::AbstractImporter::objectName, &Trade::AbstractImporter::objectCount>, "Scene name", py::arg("id")) .def("object_name", checkOpenedBoundsReturnsString<UnsignedLong, &Trade::AbstractImporter::objectName, &Trade::AbstractImporter::objectCount>, "Scene name", py::arg("id"))
.def("scene", checkOpenedBoundsResult<Trade::SceneData, &Trade::AbstractImporter::scene, &Trade::AbstractImporter::sceneCount>, "Scene", py::arg("id")) .def("scene", checkOpenedBoundsResult<Trade::SceneData, &Trade::AbstractImporter::scene, &Trade::AbstractImporter::sceneCount>, "Scene", py::arg("id"))
.def("scene", checkOpenedBoundsResultString<Trade::SceneData, &Trade::AbstractImporter::scene, &Trade::AbstractImporter::sceneForName>, "Scene for given name", py::arg("name")) .def("scene", checkOpenedBoundsResultString<Trade::SceneData, &Trade::AbstractImporter::scene, &Trade::AbstractImporter::sceneForName, &Trade::AbstractImporter::sceneCount>, "Scene for given name", py::arg("name"))
/** @todo drop std::string in favor of our own string caster */ /** @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<Trade::SceneField> { .def("scene_field_for_name", [](Trade::AbstractImporter& self, const std::string& name) -> Containers::Optional<Trade::SceneField> {
const Trade::SceneField field = self.sceneFieldForName(name); const Trade::SceneField field = self.sceneFieldForName(name);
@ -1677,7 +1714,7 @@ void trade(py::module_& m) {
.def("mesh_for_name", checkOpenedString<Int, &Trade::AbstractImporter::meshForName>, "Mesh ID for given name", py::arg("name")) .def("mesh_for_name", checkOpenedString<Int, &Trade::AbstractImporter::meshForName>, "Mesh ID for given name", py::arg("name"))
.def("mesh_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::meshName, &Trade::AbstractImporter::meshCount>, "Mesh name", py::arg("id")) .def("mesh_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::meshName, &Trade::AbstractImporter::meshCount>, "Mesh name", py::arg("id"))
.def("mesh", checkOpenedBoundsResult<Trade::MeshData, &Trade::AbstractImporter::mesh, &Trade::AbstractImporter::meshCount, &Trade::AbstractImporter::meshLevelCount>, "Mesh", py::arg("id"), py::arg("level") = 0) .def("mesh", checkOpenedBoundsResult<Trade::MeshData, &Trade::AbstractImporter::mesh, &Trade::AbstractImporter::meshCount, &Trade::AbstractImporter::meshLevelCount>, "Mesh", py::arg("id"), py::arg("level") = 0)
.def("mesh", checkOpenedBoundsResultString<Trade::MeshData, &Trade::AbstractImporter::mesh, &Trade::AbstractImporter::meshForName, &Trade::AbstractImporter::meshLevelCount>, "Mesh for given name", py::arg("name"), py::arg("level") = 0) .def("mesh", checkOpenedBoundsResultString<Trade::MeshData, &Trade::AbstractImporter::mesh, &Trade::AbstractImporter::meshForName, &Trade::AbstractImporter::meshCount, &Trade::AbstractImporter::meshLevelCount>, "Mesh for given name", py::arg("name"), py::arg("level") = 0)
/** @todo drop std::string in favor of our own string caster */ /** @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<Trade::MeshAttribute> { .def("mesh_attribute_for_name", [](Trade::AbstractImporter& self, const std::string& name) -> Containers::Optional<Trade::MeshAttribute> {
const Trade::MeshAttribute attribute = self.meshAttributeForName(name); const Trade::MeshAttribute attribute = self.meshAttributeForName(name);
@ -1696,7 +1733,7 @@ void trade(py::module_& m) {
.def("texture_for_name", checkOpenedString<Int, &Trade::AbstractImporter::textureForName>, "Texture ID for given name", py::arg("name")) .def("texture_for_name", checkOpenedString<Int, &Trade::AbstractImporter::textureForName>, "Texture ID for given name", py::arg("name"))
.def("texture_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::textureName, &Trade::AbstractImporter::textureCount>, "Texture name", py::arg("id")) .def("texture_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::textureName, &Trade::AbstractImporter::textureCount>, "Texture name", py::arg("id"))
.def("texture", checkOpenedBoundsResult<Trade::TextureData, &Trade::AbstractImporter::texture, &Trade::AbstractImporter::textureCount>, "Texture", py::arg("id")) .def("texture", checkOpenedBoundsResult<Trade::TextureData, &Trade::AbstractImporter::texture, &Trade::AbstractImporter::textureCount>, "Texture", py::arg("id"))
.def("texture", checkOpenedBoundsResultString<Trade::TextureData, &Trade::AbstractImporter::texture, &Trade::AbstractImporter::textureForName>, "Texture for given name", py::arg("name")) .def("texture", checkOpenedBoundsResultString<Trade::TextureData, &Trade::AbstractImporter::texture, &Trade::AbstractImporter::textureForName, &Trade::AbstractImporter::textureCount>, "Texture for given name", py::arg("name"))
.def_property_readonly("image1d_count", checkOpened<UnsignedInt, &Trade::AbstractImporter::image1DCount>, "One-dimensional image count") .def_property_readonly("image1d_count", checkOpened<UnsignedInt, &Trade::AbstractImporter::image1DCount>, "One-dimensional image count")
.def_property_readonly("image2d_count", checkOpened<UnsignedInt, &Trade::AbstractImporter::image2DCount>, "Two-dimensional image count") .def_property_readonly("image2d_count", checkOpened<UnsignedInt, &Trade::AbstractImporter::image2DCount>, "Two-dimensional image count")
@ -1711,11 +1748,11 @@ void trade(py::module_& m) {
.def("image2d_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::image2DName, &Trade::AbstractImporter::image2DCount>, "Two-dimensional image name", py::arg("id")) .def("image2d_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::image2DName, &Trade::AbstractImporter::image2DCount>, "Two-dimensional image name", py::arg("id"))
.def("image3d_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::image3DName, &Trade::AbstractImporter::image3DCount>, "Three-dimensional image name", py::arg("id")) .def("image3d_name", checkOpenedBoundsReturnsString<UnsignedInt, &Trade::AbstractImporter::image3DName, &Trade::AbstractImporter::image3DCount>, "Three-dimensional image name", py::arg("id"))
.def("image1d", checkOpenedBoundsResult<Trade::ImageData1D, &Trade::AbstractImporter::image1D, &Trade::AbstractImporter::image1DCount, &Trade::AbstractImporter::image1DLevelCount>, "One-dimensional image", py::arg("id"), py::arg("level") = 0) .def("image1d", checkOpenedBoundsResult<Trade::ImageData1D, &Trade::AbstractImporter::image1D, &Trade::AbstractImporter::image1DCount, &Trade::AbstractImporter::image1DLevelCount>, "One-dimensional image", py::arg("id"), py::arg("level") = 0)
.def("image1d", checkOpenedBoundsResultString<Trade::ImageData1D, &Trade::AbstractImporter::image1D, &Trade::AbstractImporter::image1DForName, &Trade::AbstractImporter::image1DLevelCount>, "One-dimensional image for given name", py::arg("name"), py::arg("level") = 0) .def("image1d", checkOpenedBoundsResultString<Trade::ImageData1D, &Trade::AbstractImporter::image1D, &Trade::AbstractImporter::image1DForName, &Trade::AbstractImporter::image1DCount, &Trade::AbstractImporter::image1DLevelCount>, "One-dimensional image for given name", py::arg("name"), py::arg("level") = 0)
.def("image2d", checkOpenedBoundsResult<Trade::ImageData2D, &Trade::AbstractImporter::image2D, &Trade::AbstractImporter::image2DCount, &Trade::AbstractImporter::image2DLevelCount>, "Two-dimensional image", py::arg("id"), py::arg("level") = 0) .def("image2d", checkOpenedBoundsResult<Trade::ImageData2D, &Trade::AbstractImporter::image2D, &Trade::AbstractImporter::image2DCount, &Trade::AbstractImporter::image2DLevelCount>, "Two-dimensional image", py::arg("id"), py::arg("level") = 0)
.def("image2d", checkOpenedBoundsResultString<Trade::ImageData2D, &Trade::AbstractImporter::image2D, &Trade::AbstractImporter::image2DForName, &Trade::AbstractImporter::image2DLevelCount>, "Two-dimensional image for given name", py::arg("name"), py::arg("level") = 0) .def("image2d", checkOpenedBoundsResultString<Trade::ImageData2D, &Trade::AbstractImporter::image2D, &Trade::AbstractImporter::image2DForName, &Trade::AbstractImporter::image2DCount, &Trade::AbstractImporter::image2DLevelCount>, "Two-dimensional image for given name", py::arg("name"), py::arg("level") = 0)
.def("image3d", checkOpenedBoundsResult<Trade::ImageData3D, &Trade::AbstractImporter::image3D, &Trade::AbstractImporter::image3DCount, &Trade::AbstractImporter::image3DLevelCount>, "Three-dimensional image", py::arg("id"), py::arg("level") = 0) .def("image3d", checkOpenedBoundsResult<Trade::ImageData3D, &Trade::AbstractImporter::image3D, &Trade::AbstractImporter::image3DCount, &Trade::AbstractImporter::image3DLevelCount>, "Three-dimensional image", py::arg("id"), py::arg("level") = 0)
.def("image3d", checkOpenedBoundsResultString<Trade::ImageData3D, &Trade::AbstractImporter::image3D, &Trade::AbstractImporter::image3DForName, &Trade::AbstractImporter::image3DLevelCount>, "Threee-dimensional image for given name", py::arg("name"), py::arg("level") = 0); .def("image3d", checkOpenedBoundsResultString<Trade::ImageData3D, &Trade::AbstractImporter::image3D, &Trade::AbstractImporter::image3DForName, &Trade::AbstractImporter::image3DCount, &Trade::AbstractImporter::image3DLevelCount>, "Threee-dimensional image for given name", py::arg("name"), py::arg("level") = 0);
py::class_<PluginManager::Manager<Trade::AbstractImporter>, PluginManager::AbstractManager> importerManager{m, "ImporterManager", "Manager for importer plugins"}; py::class_<PluginManager::Manager<Trade::AbstractImporter>, PluginManager::AbstractManager> importerManager{m, "ImporterManager", "Manager for importer plugins"};
corrade::manager(importerManager); corrade::manager(importerManager);

Loading…
Cancel
Save