Browse Source

python: expose all index/attribute property queries in trade.MeshData.

Except for the actual data access, that'll be done next. Also updated
the mesh test file with more useful contents.
next
Vladimír Vondruš 3 years ago
parent
commit
cca2eaf659
  1. 40
      doc/python/magnum.trade.rst
  2. 4
      src/python/magnum/__init__.py
  3. 112
      src/python/magnum/magnum.cpp
  4. 8
      src/python/magnum/test/convert.sh
  5. BIN
      src/python/magnum/test/mesh.bin
  6. 19
      src/python/magnum/test/mesh.bin.in
  7. BIN
      src/python/magnum/test/mesh.glb
  8. 113
      src/python/magnum/test/mesh.gltf
  9. 52
      src/python/magnum/test/test_primitives.py
  10. 151
      src/python/magnum/test/test_trade.py
  11. 130
      src/python/magnum/trade.cpp

40
doc/python/magnum.trade.rst

@ -66,8 +66,48 @@
.. py:property:: magnum.trade.ImageData3D.pixels
:raise AttributeError: If :ref:`is_compressed` is :py:`True`
.. py:class:: magnum.trade.MeshData
Compared to the C++ API, there's no
:dox:`Trade::MeshData::findAttributeId()`, the desired workflow is instead
calling :ref:`attribute_id()` and catching an exception if not found.
.. py:property:: magnum.trade.MeshData.index_count
:raise AttributeError: If :ref:`is_indexed` is :py:`False`
.. py:property:: magnum.trade.MeshData.index_type
:raise AttributeError: If :ref:`is_indexed` is :py:`False`
.. py:property:: magnum.trade.MeshData.index_offset
:raise AttributeError: If :ref:`is_indexed` is :py:`False`
.. py:property:: magnum.trade.MeshData.index_stride
:raise AttributeError: If :ref:`is_indexed` is :py:`False`
.. py:function:: magnum.trade.MeshData.attribute_name
:raise IndexError: If :p:`id` is negative or not less than
:ref:`attribute_count()`
.. py:function:: magnum.trade.MeshData.attribute_id
:raise IndexError: If :p:`id` is negative or not less than
:ref:`attribute_count()`
:raise KeyError: If :p:`id` is negative or not less than
:ref:`attribute_count()` for :p:`name`
.. py:function:: magnum.trade.MeshData.attribute_format
:raise IndexError: If :p:`id` is negative or not less than
:ref:`attribute_count()`
:raise KeyError: If :p:`id` is negative or not less than
:ref:`attribute_count()` for :p:`name`
.. py:function:: magnum.trade.MeshData.attribute_offset
:raise IndexError: If :p:`id` is negative or not less than
:ref:`attribute_count()`
:raise KeyError: If :p:`id` is negative or not less than
:ref:`attribute_count()` for :p:`name`
.. py:function:: magnum.trade.MeshData.attribute_stride
:raise IndexError: If :p:`id` is negative or not less than
:ref:`attribute_count()`
:raise KeyError: If :p:`id` is negative or not less than
:ref:`attribute_count()` for :p:`name`
.. py:function:: magnum.trade.MeshData.attribute_array_size
:raise IndexError: If :p:`id` is negative or not less than
:ref:`attribute_count()`
:raise KeyError: If :p:`id` is negative or not less than
:ref:`attribute_count()` for :p:`name`
.. py:class:: magnum.trade.ImporterManager
:summary: Manager for :ref:`AbstractImporter` plugin instances

4
src/python/magnum/__init__.py

@ -78,7 +78,9 @@ __all__ = [
'ImageView1D', 'ImageView2D', 'ImageView3D',
'MutableImageView1D', 'MutableImageView2D', 'MutableImageView3D',
'SamplerFilter', 'SamplerMipmap', 'SamplerWrapping'
'SamplerFilter', 'SamplerMipmap', 'SamplerWrapping',
'VertexFormat'
# TARGET_*, BUILD_* are omitted as `from magnum import *` would pull them
# to globals and this would likely cause conflicts (corrade also defines

112
src/python/magnum/magnum.cpp

@ -32,6 +32,7 @@
#include <Magnum/PixelFormat.h>
#include <Magnum/PixelStorage.h>
#include <Magnum/Sampler.h>
#include <Magnum/VertexFormat.h>
#include "Corrade/PythonBindings.h"
#include "Corrade/Containers/PythonBindings.h"
@ -336,6 +337,117 @@ void magnum(py::module_& m) {
.value("CLAMP_TO_EDGE", SamplerWrapping::ClampToEdge)
.value("CLAMP_TO_BORDER", SamplerWrapping::ClampToBorder)
.value("MIRROR_CLAMP_TO_EDGE", SamplerWrapping::MirrorClampToEdge);
py::enum_<VertexFormat>{m, "VertexFormat", "Vertex format"}
.value("FLOAT", VertexFormat::Float)
.value("HALF", VertexFormat::Half)
.value("DOUBLE", VertexFormat::Double)
.value("UNSIGNED_BYTE", VertexFormat::UnsignedByte)
.value("UNSIGNED_BYTE_NORMALIZED", VertexFormat::UnsignedByteNormalized)
.value("BYTE", VertexFormat::Byte)
.value("BYTE_NORMALIZED", VertexFormat::ByteNormalized)
.value("UNSIGNED_SHORT", VertexFormat::UnsignedShort)
.value("UNSIGNED_SHORT_NORMALIZED", VertexFormat::UnsignedShortNormalized)
.value("SHORT", VertexFormat::Short)
.value("SHORT_NORMALIZED", VertexFormat::ShortNormalized)
.value("UNSIGNED_INT", VertexFormat::UnsignedInt)
.value("INT", VertexFormat::Int)
.value("VECTOR2", VertexFormat::Vector2)
.value("VECTOR2H", VertexFormat::Vector2h)
.value("VECTOR2D", VertexFormat::Vector2d)
.value("VECTOR2UB", VertexFormat::Vector2ub)
.value("VECTOR2UB_NORMALIZED", VertexFormat::Vector2ubNormalized)
.value("VECTOR2B", VertexFormat::Vector2b)
.value("VECTOR2B_NORMALIZED", VertexFormat::Vector2bNormalized)
.value("VECTOR2US", VertexFormat::Vector2us)
.value("VECTOR2US_NORMALIZED", VertexFormat::Vector2usNormalized)
.value("VECTOR2S", VertexFormat::Vector2s)
.value("VECTOR2S_NORMALIZED", VertexFormat::Vector2usNormalized)
.value("VECTOR2UI", VertexFormat::Vector2ui)
.value("VECTOR2I", VertexFormat::Vector2i)
.value("VECTOR3", VertexFormat::Vector3)
.value("VECTOR3H", VertexFormat::Vector3h)
.value("VECTOR3D", VertexFormat::Vector3d)
.value("VECTOR3UB", VertexFormat::Vector3ub)
.value("VECTOR3UB_NORMALIZED", VertexFormat::Vector3ubNormalized)
.value("VECTOR3B", VertexFormat::Vector3b)
.value("VECTOR3B_NORMALIZED", VertexFormat::Vector3bNormalized)
.value("VECTOR3US", VertexFormat::Vector3us)
.value("VECTOR3US_NORMALIZED", VertexFormat::Vector3usNormalized)
.value("VECTOR3S", VertexFormat::Vector3s)
.value("VECTOR3S_NORMALIZED", VertexFormat::Vector3usNormalized)
.value("VECTOR3UI", VertexFormat::Vector3ui)
.value("VECTOR3I", VertexFormat::Vector3i)
.value("VECTOR4", VertexFormat::Vector4)
.value("VECTOR4H", VertexFormat::Vector4h)
.value("VECTOR4D", VertexFormat::Vector4d)
.value("VECTOR4UB", VertexFormat::Vector4ub)
.value("VECTOR4UB_NORMALIZED", VertexFormat::Vector4ubNormalized)
.value("VECTOR4B", VertexFormat::Vector4b)
.value("VECTOR4B_NORMALIZED", VertexFormat::Vector4bNormalized)
.value("VECTOR4US", VertexFormat::Vector4us)
.value("VECTOR4US_NORMALIZED", VertexFormat::Vector4usNormalized)
.value("VECTOR4S", VertexFormat::Vector4s)
.value("VECTOR4S_NORMALIZED", VertexFormat::Vector4usNormalized)
.value("VECTOR4UI", VertexFormat::Vector4ui)
.value("VECTOR4I", VertexFormat::Vector4i)
.value("MATRIX2X2", VertexFormat::Matrix2x2)
.value("MATRIX2X2H", VertexFormat::Matrix2x2h)
.value("MATRIX2X2D", VertexFormat::Matrix2x2d)
.value("MATRIX2X2B_NORMALIZED", VertexFormat::Matrix2x2bNormalized)
.value("MATRIX2X2S_NORMALIZED", VertexFormat::Matrix2x2sNormalized)
.value("MATRIX2X3", VertexFormat::Matrix2x3)
.value("MATRIX2X3H", VertexFormat::Matrix2x3h)
.value("MATRIX2X3D", VertexFormat::Matrix2x3d)
.value("MATRIX2X3B_NORMALIZED", VertexFormat::Matrix2x3bNormalized)
.value("MATRIX2X3S_NORMALIZED", VertexFormat::Matrix2x3sNormalized)
.value("MATRIX2X4", VertexFormat::Matrix2x4)
.value("MATRIX2X4H", VertexFormat::Matrix2x4h)
.value("MATRIX2X4D", VertexFormat::Matrix2x4d)
.value("MATRIX2X4B_NORMALIZED", VertexFormat::Matrix2x4bNormalized)
.value("MATRIX2X4S_NORMALIZED", VertexFormat::Matrix2x4sNormalized)
.value("MATRIX2X2B_NORMALIZED_ALIGNED", VertexFormat::Matrix2x2bNormalizedAligned)
.value("MATRIX2X3H_ALIGNED", VertexFormat::Matrix2x3hAligned)
.value("MATRIX2X3B_NORMALIZED_ALIGNED", VertexFormat::Matrix2x3bNormalizedAligned)
.value("MATRIX2X3S_NORMALIZED_ALIGNED", VertexFormat::Matrix2x3sNormalizedAligned)
.value("MATRIX3X2", VertexFormat::Matrix3x2)
.value("MATRIX3X2H", VertexFormat::Matrix3x2h)
.value("MATRIX3X2D", VertexFormat::Matrix3x2d)
.value("MATRIX3X2B_NORMALIZED", VertexFormat::Matrix3x2bNormalized)
.value("MATRIX3X2S_NORMALIZED", VertexFormat::Matrix3x2sNormalized)
.value("MATRIX3X3", VertexFormat::Matrix3x3)
.value("MATRIX3X3H", VertexFormat::Matrix3x3h)
.value("MATRIX3X3D", VertexFormat::Matrix3x3d)
.value("MATRIX3X3B_NORMALIZED", VertexFormat::Matrix3x3bNormalized)
.value("MATRIX3X3S_NORMALIZED", VertexFormat::Matrix3x3sNormalized)
.value("MATRIX3X4", VertexFormat::Matrix3x4)
.value("MATRIX3X4H", VertexFormat::Matrix3x4h)
.value("MATRIX3X4D", VertexFormat::Matrix3x4d)
.value("MATRIX3X4B_NORMALIZED", VertexFormat::Matrix3x4bNormalized)
.value("MATRIX3X4S_NORMALIZED", VertexFormat::Matrix3x4sNormalized)
.value("MATRIX3X2B_NORMALIZED_ALIGNED", VertexFormat::Matrix3x2bNormalizedAligned)
.value("MATRIX3X3H_ALIGNED", VertexFormat::Matrix3x3hAligned)
.value("MATRIX3X3B_NORMALIZED_ALIGNED", VertexFormat::Matrix3x3bNormalizedAligned)
.value("MATRIX3X3S_NORMALIZED_ALIGNED", VertexFormat::Matrix3x3sNormalizedAligned)
.value("MATRIX4X2", VertexFormat::Matrix4x2)
.value("MATRIX4X2H", VertexFormat::Matrix4x2h)
.value("MATRIX4X2D", VertexFormat::Matrix4x2d)
.value("MATRIX4X2B_NORMALIZED", VertexFormat::Matrix4x2bNormalized)
.value("MATRIX4X2S_NORMALIZED", VertexFormat::Matrix4x2sNormalized)
.value("MATRIX4X3", VertexFormat::Matrix4x3)
.value("MATRIX4X3H", VertexFormat::Matrix4x3h)
.value("MATRIX4X3D", VertexFormat::Matrix4x3d)
.value("MATRIX4X3B_NORMALIZED", VertexFormat::Matrix4x3bNormalized)
.value("MATRIX4X3S_NORMALIZED", VertexFormat::Matrix4x3sNormalized)
.value("MATRIX4X4", VertexFormat::Matrix4x4)
.value("MATRIX4X4H", VertexFormat::Matrix4x4h)
.value("MATRIX4X4D", VertexFormat::Matrix4x4d)
.value("MATRIX4X4B_NORMALIZED", VertexFormat::Matrix4x4bNormalized)
.value("MATRIX4X4S_NORMALIZED", VertexFormat::Matrix4x4sNormalized)
.value("MATRIX4X2B_NORMALIZED_ALIGNED", VertexFormat::Matrix4x2bNormalizedAligned)
.value("MATRIX4X3H_ALIGNED", VertexFormat::Matrix4x3hAligned)
.value("MATRIX4X3B_NORMALIZED_ALIGNED", VertexFormat::Matrix4x3bNormalizedAligned)
.value("MATRIX4X3S_NORMALIZED_ALIGNED", VertexFormat::Matrix4x3sNormalizedAligned);
}
}}

8
src/python/magnum/test/convert.sh

@ -0,0 +1,8 @@
#!/bin/bash
set -e
# in -> bin
for i in *.bin.in; do
../../../../../magnum-plugins/src/MagnumPlugins/GltfImporter/Test/in2bin.py $i
done

BIN
src/python/magnum/test/mesh.bin

Binary file not shown.

19
src/python/magnum/test/mesh.bin.in

@ -0,0 +1,19 @@
type = '<xx3H 3f2f4BI 3f2f4BI 3f2f4BI'
input = [
# 16-bit indices, with a two-byte padding before (thus 8 bytes in total)
0, 2, 1,
# interleaved:
# - positions as 32-bit floats, aliased together with first texture coord
# with joint weights,
# - texture coords as floats,
# - normalized three-component 8-bit colors aliased with four-component
# joint IDs,
# - one byte padding for alignment,
# - 32-bit integer object IDs
-1.0, -1.0, 0.25, 0.1, 0.2, 255, 51, 102, 3, 216,
0.0, 1.0, 0.50, 0.3, 0.4, 51, 102, 255, 0, 16777235,
1.0, -1.0, 0.25, 0.5, 0.6, 103, 255, 51, 7, 2872872013
]
# kate: hl python

BIN
src/python/magnum/test/mesh.glb

Binary file not shown.

113
src/python/magnum/test/mesh.gltf

@ -0,0 +1,113 @@
{
"asset": {
"version": "2.0"
},
"meshes": [
{
"name": "Indexed mesh",
"primitives": [
{
"attributes": {
"POSITION": 1,
"TEXCOORD_0": 2,
"COLOR": 3,
"_OBJECT_ID": 4,
"TEXCOORD_1": 2,
"JOINTS_0": 5,
"WEIGHTS_0": 6,
"_CUSTOM_ATTRIBUTE": 7
},
"indices": 0
}
]
},
{
"name": "Non-indexed mesh",
"primitives": [
{
"attributes": {
"POSITION": 1
}
}
]
}
],
"accessors": [
{
"bufferView": 0,
"byteOffset": 2,
"componentType": 5123,
"count": 3,
"type": "SCALAR"
},
{
"bufferView": 1,
"componentType": 5126,
"count": 3,
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 12,
"componentType": 5126,
"count": 3,
"type": "VEC2"
},
{
"bufferView": 1,
"byteOffset": 20,
"componentType": 5121,
"normalized": true,
"count": 3,
"type": "VEC3"
},
{
"bufferView": 1,
"byteOffset": 24,
"componentType": 5125,
"count": 3,
"type": "SCALAR"
},
{
"bufferView": 1,
"byteOffset": 20,
"componentType": 5121,
"count": 3,
"type": "VEC4"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 3,
"type": "VEC4"
},
{
"bufferView": 1,
"byteOffset": 0,
"componentType": 5126,
"count": 3,
"type": "MAT2"
}
],
"bufferViews": [
{
"buffer": 0,
"byteOffset": 0,
"byteLength": 8
},
{
"buffer": 0,
"byteOffset": 8,
"byteLength": 84,
"byteStride": 28
}
],
"buffers": [
{
"byteLength": 92,
"uri": "mesh.bin"
}
]
}

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

@ -33,30 +33,30 @@ class Axis(unittest.TestCase):
a = primitives.axis2d()
self.assertEqual(a.primitive, MeshPrimitive.LINES)
self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 2)
self.assertEqual(a.attribute_count(), 2)
def test_3d(self):
a = primitives.axis3d()
self.assertEqual(a.primitive, MeshPrimitive.LINES)
self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 2)
self.assertEqual(a.attribute_count(), 2)
class Capsule(unittest.TestCase):
def test_2d_wireframe(self):
a = primitives.capsule2d_wireframe(3, 3, 2.0)
self.assertEqual(a.primitive, MeshPrimitive.LINES)
self.assertEqual(a.attribute_count, 1)
self.assertEqual(a.attribute_count(), 1)
def test_3d_solid(self):
a = primitives.capsule3d_solid(3, 3, 10, 2.0, primitives.CapsuleFlags.TEXTURE_COORDINATES|primitives.CapsuleFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 4)
self.assertEqual(a.attribute_count(), 4)
b = primitives.capsule3d_solid(3, 3, 10, 2.0)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(b.is_indexed)
self.assertEqual(b.attribute_count, 2)
self.assertEqual(b.attribute_count(), 2)
def test_3d_wireframe(self):
a = primitives.capsule3d_wireframe(5, 3, 12, 0.3)
@ -68,12 +68,12 @@ class Circle(unittest.TestCase):
a = primitives.circle2d_solid(5, primitives.Circle2DFlags.TEXTURE_COORDINATES)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_FAN)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2)
self.assertEqual(a.attribute_count(), 2)
b = primitives.circle2d_solid(5)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_FAN)
self.assertFalse(b.is_indexed)
self.assertEqual(b.attribute_count, 1)
self.assertEqual(b.attribute_count(), 1)
def test_2d_wireframe(self):
a = primitives.circle2d_wireframe(5)
@ -84,12 +84,12 @@ class Circle(unittest.TestCase):
a = primitives.circle3d_solid(5, primitives.Circle3DFlags.TEXTURE_COORDINATES|primitives.Circle3DFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_FAN)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 4)
self.assertEqual(a.attribute_count(), 4)
b = primitives.circle3d_solid(5)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_FAN)
self.assertFalse(b.is_indexed)
self.assertEqual(b.attribute_count, 2)
self.assertEqual(b.attribute_count(), 2)
def test_3d_wireframe(self):
a = primitives.circle3d_wireframe(5)
@ -101,12 +101,12 @@ class Cone(unittest.TestCase):
a = primitives.cone_solid(5, 7, 7.1, primitives.ConeFlags.TEXTURE_COORDINATES|primitives.ConeFlags.CAP_END)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 3)
self.assertEqual(a.attribute_count(), 3)
b = primitives.cone_solid(5, 7, 7.1)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(b.is_indexed)
self.assertEqual(b.attribute_count, 2)
self.assertEqual(b.attribute_count(), 2)
def test_wireframe(self):
a = primitives.cone_wireframe(16, 7.1)
@ -145,12 +145,12 @@ class Cylinder(unittest.TestCase):
a = primitives.cylinder_solid(7, 12, 0.2, primitives.CylinderFlags.TEXTURE_COORDINATES|primitives.CylinderFlags.CAP_ENDS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 3)
self.assertEqual(a.attribute_count(), 3)
b = primitives.cylinder_solid(7, 12, 0.2)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(b.is_indexed)
self.assertEqual(b.attribute_count, 2)
self.assertEqual(b.attribute_count(), 2)
def test_wireframe(self):
a = primitives.cylinder_wireframe(8, 16, 1.1)
@ -162,44 +162,44 @@ class Gradient(unittest.TestCase):
a = primitives.gradient2d((3.1, 2.0), Color3(), (0.2, 1.1), Color4())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2)
self.assertEqual(a.attribute_count(), 2)
def test_gradient2d_horizontal(self):
a = primitives.gradient2d_horizontal(Color4(), Color3())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2)
self.assertEqual(a.attribute_count(), 2)
def test_gradient2d_vertical(self):
a = primitives.gradient2d_vertical(Color4(), Color3())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2)
self.assertEqual(a.attribute_count(), 2)
def test_gradient3d(self):
a = primitives.gradient3d((3.1, 2.0, 0.1), Color3(), (0.2, 1.1, 1.2), Color4())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 3)
self.assertEqual(a.attribute_count(), 3)
def test_gradient3d_horizontal(self):
a = primitives.gradient3d_horizontal(Color4(), Color3())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 3)
self.assertEqual(a.attribute_count(), 3)
def test_gradient3d_vertical(self):
a = primitives.gradient3d_vertical(Color4(), Color3())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 3)
self.assertEqual(a.attribute_count(), 3)
class Grid(unittest.TestCase):
def test_solid(self):
a = primitives.grid3d_solid((4, 5), primitives.GridFlags.NORMALS|primitives.GridFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 3)
self.assertEqual(a.attribute_count(), 3)
def test_wireframe(self):
a = primitives.grid3d_wireframe((2, 7))
@ -238,12 +238,12 @@ class Plane(unittest.TestCase):
a = primitives.plane_solid(primitives.PlaneFlags.TEXTURE_COORDINATES|primitives.PlaneFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 4)
self.assertEqual(a.attribute_count(), 4)
b = primitives.plane_solid()
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(b.is_indexed)
self.assertEqual(b.attribute_count, 2)
self.assertEqual(b.attribute_count(), 2)
def test_wireframe(self):
a = primitives.plane_wireframe()
@ -255,12 +255,12 @@ class Square(unittest.TestCase):
a = primitives.square_solid(primitives.SquareFlags.TEXTURE_COORDINATES)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2)
self.assertEqual(a.attribute_count(), 2)
b = primitives.square_solid()
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(b.is_indexed)
self.assertEqual(b.attribute_count, 1)
self.assertEqual(b.attribute_count(), 1)
def test_wireframe(self):
a = primitives.square_wireframe()
@ -272,12 +272,12 @@ class UVSphere(unittest.TestCase):
a = primitives.uv_sphere_solid(3, 7, primitives.UVSphereFlags.TEXTURE_COORDINATES|primitives.UVSphereFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 4)
self.assertEqual(a.attribute_count(), 4)
b = primitives.uv_sphere_solid(3, 7)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(b.is_indexed)
self.assertEqual(b.attribute_count, 2)
self.assertEqual(b.attribute_count(), 2)
def test_wireframe(self):
a = primitives.uv_sphere_wireframe(6, 8)

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

@ -101,21 +101,62 @@ class ImageData(unittest.TestCase):
class MeshData(unittest.TestCase):
def test(self):
# The only way to get a mesh instance is through a manager
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
mesh = importer.mesh(1)
mesh = importer.mesh(0)
mesh_refcount = sys.getrefcount(mesh)
self.assertTrue(mesh.is_indexed)
self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES)
self.assertEqual(mesh.vertex_count, 3)
# Index properties
self.assertTrue(mesh.is_indexed)
self.assertEqual(mesh.index_count, 3)
self.assertEqual(mesh.attribute_count, 2)
# TODO: test more, once it's exposed
self.assertEqual(mesh.index_type, MeshIndexType.UNSIGNED_SHORT)
self.assertEqual(mesh.index_offset, 0)
self.assertEqual(mesh.index_stride, 2)
self.assertEqual(mesh.vertex_count, 3)
# TODO once configuration is exposed, disable the JOINTS/WEIGHTS
# backwards compatibility to reduce useless attribute count
self.assertEqual(mesh.attribute_count(), 10)
# Attribute properties by ID
self.assertEqual(mesh.attribute_name(3), trade.MeshAttribute.POSITION)
# Custom attribute
# TODO better API for custom IDs?
self.assertEqual(mesh.attribute_name(8), trade.MeshAttribute(32768 + 9))
self.assertEqual(mesh.attribute_id(3), 0)
# Attribute 5 is the second TEXTURE_COORDINATES attribute
self.assertEqual(mesh.attribute_id(5), 1)
self.assertEqual(mesh.attribute_format(0), VertexFormat.VECTOR3UB_NORMALIZED)
self.assertEqual(mesh.attribute_format(9), VertexFormat.UNSIGNED_INT)
self.assertEqual(mesh.attribute_offset(0), 20)
self.assertEqual(mesh.attribute_offset(3), 0)
self.assertEqual(mesh.attribute_stride(2), 28)
self.assertEqual(mesh.attribute_array_size(0), 0)
# Attribute 1 is JOINT_IDS
self.assertEqual(mesh.attribute_array_size(1), 4)
# Attribute properties by name
self.assertTrue(mesh.has_attribute(trade.MeshAttribute.COLOR))
self.assertTrue(mesh.has_attribute(trade.MeshAttribute.POSITION))
self.assertFalse(mesh.has_attribute(trade.MeshAttribute.TANGENT))
self.assertEqual(mesh.attribute_count(trade.MeshAttribute.POSITION), 1)
self.assertEqual(mesh.attribute_count(trade.MeshAttribute.TEXTURE_COORDINATES), 2)
self.assertEqual(mesh.attribute_count(trade.MeshAttribute.TANGENT), 0)
self.assertEqual(mesh.attribute_id(trade.MeshAttribute.POSITION), 3)
self.assertEqual(mesh.attribute_id(trade.MeshAttribute.TEXTURE_COORDINATES, 1), 5)
self.assertEqual(mesh.attribute_format(trade.MeshAttribute.COLOR), VertexFormat.VECTOR3UB_NORMALIZED)
self.assertEqual(mesh.attribute_format(trade.MeshAttribute.OBJECT_ID), VertexFormat.UNSIGNED_INT)
self.assertEqual(mesh.attribute_offset(trade.MeshAttribute.COLOR), 20)
self.assertEqual(mesh.attribute_offset(trade.MeshAttribute.POSITION), 0)
self.assertEqual(mesh.attribute_stride(trade.MeshAttribute.WEIGHTS), 28)
self.assertEqual(mesh.attribute_array_size(trade.MeshAttribute.POSITION), 0)
self.assertEqual(mesh.attribute_array_size(trade.MeshAttribute.WEIGHTS), 4)
index_data = mesh.index_data
self.assertEqual(len(index_data), 3)
self.assertEqual(len(index_data), 6)
self.assertIs(index_data.owner, mesh)
self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1)
@ -123,7 +164,7 @@ class MeshData(unittest.TestCase):
self.assertEqual(sys.getrefcount(mesh), mesh_refcount)
vertex_data = mesh.vertex_data
self.assertEqual(len(vertex_data), 72)
self.assertEqual(len(vertex_data), 84)
self.assertIs(vertex_data.owner, mesh)
self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1)
@ -131,11 +172,10 @@ class MeshData(unittest.TestCase):
self.assertEqual(sys.getrefcount(mesh), mesh_refcount)
def test_nonindexed(self):
# The only way to get a mesh instance is through a manager
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
mesh = importer.mesh(0)
mesh = importer.mesh(1)
self.assertFalse(mesh.is_indexed)
# Accessing the index data should be possible, they're just empty
@ -144,6 +184,56 @@ class MeshData(unittest.TestCase):
# Accessing any other index-related info should cause an exception
with self.assertRaisesRegex(AttributeError, "mesh is not indexed"):
mesh.index_count
with self.assertRaisesRegex(AttributeError, "mesh is not indexed"):
mesh.index_type
with self.assertRaisesRegex(AttributeError, "mesh is not indexed"):
mesh.index_offset
with self.assertRaisesRegex(AttributeError, "mesh is not indexed"):
mesh.index_stride
def test_attribute_oob(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
mesh = importer.mesh(0)
# Access by OOB ID
with self.assertRaises(IndexError):
mesh.attribute_name(mesh.attribute_count())
with self.assertRaises(IndexError):
mesh.attribute_id(mesh.attribute_count())
with self.assertRaises(IndexError):
mesh.attribute_format(mesh.attribute_count())
with self.assertRaises(IndexError):
mesh.attribute_offset(mesh.attribute_count())
with self.assertRaises(IndexError):
mesh.attribute_stride(mesh.attribute_count())
with self.assertRaises(IndexError):
mesh.attribute_array_size(mesh.attribute_count())
# Access by nonexistent name
with self.assertRaises(KeyError):
mesh.attribute_id(trade.MeshAttribute.TANGENT)
with self.assertRaises(KeyError):
mesh.attribute_format(trade.MeshAttribute.TANGENT)
with self.assertRaises(KeyError):
mesh.attribute_offset(trade.MeshAttribute.TANGENT)
with self.assertRaises(KeyError):
mesh.attribute_stride(trade.MeshAttribute.TANGENT)
with self.assertRaises(KeyError):
mesh.attribute_array_size(trade.MeshAttribute.TANGENT)
# Access by existing name + OOB ID
with self.assertRaises(KeyError):
mesh.attribute_id(trade.MeshAttribute.TEXTURE_COORDINATES, 2)
with self.assertRaises(KeyError):
mesh.attribute_format(trade.MeshAttribute.TEXTURE_COORDINATES, 2)
with self.assertRaises(KeyError):
mesh.attribute_offset(trade.MeshAttribute.TEXTURE_COORDINATES, 2)
with self.assertRaises(KeyError):
mesh.attribute_stride(trade.MeshAttribute.TEXTURE_COORDINATES, 2)
with self.assertRaises(KeyError):
mesh.attribute_array_size(trade.MeshAttribute.TEXTURE_COORDINATES, 2)
class Importer(unittest.TestCase):
def test(self):
@ -257,39 +347,52 @@ class Importer(unittest.TestCase):
def test_mesh(self):
# importer refcounting tested in image2d
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb'))
self.assertEqual(importer.mesh_count, 3)
# Asking for custom mesh attribute names should work even if not
# opened, returns None
# TODO better API for custom IDs?
self.assertIsNone(importer.mesh_attribute_name(trade.MeshAttribute(32768 + 9)))
self.assertIsNone(importer.mesh_attribute_for_name("_CUSTOM_ATTRIBUTE"))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
self.assertEqual(importer.mesh_count, 2)
self.assertEqual(importer.mesh_level_count(0), 1)
self.assertEqual(importer.mesh_name(0), 'Non-indexed mesh')
self.assertEqual(importer.mesh_for_name('Non-indexed mesh'), 0)
self.assertEqual(importer.mesh_name(0), 'Indexed mesh')
self.assertEqual(importer.mesh_for_name('Indexed mesh'), 0)
# It should work after opening
self.assertEqual(importer.mesh_attribute_name(trade.MeshAttribute(32768 + 9)), "_CUSTOM_ATTRIBUTE")
# TODO better API for custom IDs?
self.assertEqual(importer.mesh_attribute_for_name("_CUSTOM_ATTRIBUTE"), trade.MeshAttribute(32768 + 9))
mesh = importer.mesh(0)
self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(mesh.has_attribute(importer.mesh_attribute_for_name("_CUSTOM_ATTRIBUTE")))
def test_mesh_level_oob(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
with self.assertRaises(IndexError):
importer.mesh(0, 1)
def test_mesh_by_name(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
mesh = importer.mesh('Non-indexed mesh')
self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES)
def test_mesh_by_name_not_found(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
with self.assertRaises(KeyError):
importer.mesh('Nonexistent')
def test_mesh_by_name_level_oob(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb'))
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
with self.assertRaises(IndexError):
importer.mesh('Non-indexed mesh', 1)
@ -369,8 +472,8 @@ class ImageConverter(unittest.TestCase):
class SceneConverter(unittest.TestCase):
def test_mesh(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb'))
mesh = importer.mesh(0)
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
mesh = importer.mesh(1)
converter = trade.SceneConverterManager().load_and_instantiate('StanfordSceneConverter')
@ -380,8 +483,8 @@ class SceneConverter(unittest.TestCase):
def test_mesh_failed(self):
importer = trade.ImporterManager().load_and_instantiate('GltfImporter')
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.glb'))
mesh = importer.mesh(0)
importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
mesh = importer.mesh(1)
converter = trade.SceneConverterManager().load_and_instantiate('AnySceneConverter')

130
src/python/magnum/trade.cpp

@ -35,6 +35,7 @@
#include <Magnum/Trade/MeshData.h>
#include "Corrade/Containers/PythonBindings.h"
#include "Corrade/Containers/OptionalPythonBindings.h"
#include "Corrade/Containers/StridedArrayViewPythonBindings.h"
#include "Magnum/PythonBindings.h"
@ -305,11 +306,24 @@ void trade(py::module_& m) {
/* AbstractImporter depends on this */
py::module_::import("corrade.pluginmanager");
py::enum_<Trade::MeshAttribute>{m, "MeshAttribute", "Mesh attribute name"}
.value("POSITION", Trade::MeshAttribute::Position)
.value("TANGENT", Trade::MeshAttribute::Tangent)
.value("BITANGENT", Trade::MeshAttribute::Bitangent)
.value("NORMAL", Trade::MeshAttribute::Normal)
.value("TEXTURE_COORDINATES", Trade::MeshAttribute::TextureCoordinates)
.value("COLOR", Trade::MeshAttribute::Color)
.value("JOINT_IDS", Trade::MeshAttribute::JointIds)
.value("WEIGHTS", Trade::MeshAttribute::Weights)
.value("OBJECT_ID", Trade::MeshAttribute::ObjectId);
py::class_<Trade::MeshData>{m, "MeshData", "Mesh data"}
.def_property_readonly("primitive", &Trade::MeshData::primitive, "Primitive")
.def_property_readonly("index_data", [](Trade::MeshData& self) {
return Containers::pyArrayViewHolder(self.indexData(), py::cast(self));
}, "Raw index data")
/** @todo direct access to MeshAttributeData, once making custom
MeshData is desired */
.def_property_readonly("vertex_data", [](Trade::MeshData& self) {
return Containers::pyArrayViewHolder(self.vertexData(), py::cast(self));
}, "Raw vertex data")
@ -319,11 +333,109 @@ void trade(py::module_& m) {
PyErr_SetString(PyExc_AttributeError, "mesh is not indexed");
throw py::error_already_set{};
}
return self.indexCount();
}, "Index count")
.def_property_readonly("index_type", [](Trade::MeshData& self) {
if(!self.isIndexed()) {
PyErr_SetString(PyExc_AttributeError, "mesh is not indexed");
throw py::error_already_set{};
}
return self.indexType();
}, "Index type")
.def_property_readonly("index_offset", [](Trade::MeshData& self) {
if(!self.isIndexed()) {
PyErr_SetString(PyExc_AttributeError, "mesh is not indexed");
throw py::error_already_set{};
}
return self.indexOffset();
}, "Index offset")
.def_property_readonly("index_stride", [](Trade::MeshData& self) {
if(!self.isIndexed()) {
PyErr_SetString(PyExc_AttributeError, "mesh is not indexed");
throw py::error_already_set{};
}
return self.indexStride();
}, "Index stride")
.def_property_readonly("vertex_count", &Trade::MeshData::vertexCount, "Vertex count")
.def_property_readonly("attribute_count", static_cast<UnsignedInt(Trade::MeshData::*)() const>(&Trade::MeshData::attributeCount), "Attribute array count");
/* Has to be a function instead of a property because there's an
overload taking a name */
.def("attribute_count", static_cast<UnsignedInt(Trade::MeshData::*)() const>(&Trade::MeshData::attributeCount), "Attribute array count")
/** @todo direct access to MeshAttributeData, once making custom
MeshData is desired */
.def("attribute_name", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) {
PyErr_SetString(PyExc_IndexError, "");
throw py::error_already_set{};
}
return self.attributeName(id);
}, "Attribute name", py::arg("id"))
.def("attribute_id", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) {
PyErr_SetString(PyExc_IndexError, "");
throw py::error_already_set{};
}
return self.attributeId(id);
}, "Attribute ID in a set of attributes of the same name", py::arg("id"))
.def("attribute_format", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) {
PyErr_SetString(PyExc_IndexError, "");
throw py::error_already_set{};
}
return self.attributeFormat(id);
}, "Attribute format", py::arg("id"))
.def("attribute_offset", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) {
PyErr_SetString(PyExc_IndexError, "");
throw py::error_already_set{};
}
return self.attributeOffset(id);
}, "Attribute offset", py::arg("id"))
.def("attribute_stride", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) {
PyErr_SetString(PyExc_IndexError, "");
throw py::error_already_set{};
}
return self.attributeStride(id);
}, "Attribute stride", py::arg("id"))
.def("attribute_array_size", [](Trade::MeshData& self, UnsignedInt id) {
if(id >= self.attributeCount()) {
PyErr_SetString(PyExc_IndexError, "");
throw py::error_already_set{};
}
return self.attributeArraySize(id);
}, "Attribute array size", py::arg("id"))
.def("has_attribute", &Trade::MeshData::hasAttribute, "Whether the mesh has given attribute", py::arg("name"))
.def("attribute_count", static_cast<UnsignedInt(Trade::MeshData::*)(Trade::MeshAttribute) const>(&Trade::MeshData::attributeCount), "Count of given named attribute", py::arg("name"))
.def("attribute_id", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id))
return *found;
PyErr_SetString(PyExc_KeyError, "");
throw py::error_already_set{};
}, "Absolute ID of a named attribute", py::arg("name"), py::arg("id") = 0)
.def("attribute_format", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id))
return self.attributeFormat(*found);
PyErr_SetString(PyExc_KeyError, "");
throw py::error_already_set{};
}, "Format of a named attribute", py::arg("name"), py::arg("id") = 0)
.def("attribute_offset", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id))
return self.attributeOffset(*found);
PyErr_SetString(PyExc_KeyError, "");
throw py::error_already_set{};
}, "Offset of a named attribute", py::arg("name"), py::arg("id") = 0)
.def("attribute_stride", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id))
return self.attributeStride(*found);
PyErr_SetString(PyExc_KeyError, "");
throw py::error_already_set{};
}, "Stride of a named attribute", py::arg("name"), py::arg("id") = 0)
.def("attribute_array_size", [](Trade::MeshData& self, Trade::MeshAttribute name, UnsignedInt id) {
if(const Containers::Optional<UnsignedInt> found = self.findAttributeId(name, id))
return self.attributeArraySize(*found);
PyErr_SetString(PyExc_KeyError, "");
throw py::error_already_set{};
}, "Array size of a named attribute", py::arg("name"), py::arg("id") = 0);
py::class_<Trade::ImageData1D> imageData1D{m, "ImageData1D", "One-dimensional image data"};
py::class_<Trade::ImageData2D> imageData2D{m, "ImageData2D", "Two-dimensional image data"};
@ -367,7 +479,19 @@ void trade(py::module_& m) {
.def("mesh_name", checkOpenedBoundsReturnsString<&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", checkOpenedBoundsResultString<Trade::MeshData, &Trade::AbstractImporter::mesh, &Trade::AbstractImporter::meshForName, &Trade::AbstractImporter::meshLevelCount>, "Mesh", py::arg("name"), py::arg("level") = 0)
/** @todo mesh_attribute_for_name / mesh_attribute_name */
/** @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> {
const Trade::MeshAttribute attribute = self.meshAttributeForName(name);
if(attribute == Trade::MeshAttribute{})
return {};
return attribute;
}, "Mesh attribute for given name", py::arg("name"))
/** @todo drop std::string in favor of our own string caster */
.def("mesh_attribute_name", [](Trade::AbstractImporter& self, Trade::MeshAttribute name) -> Containers::Optional<std::string> {
if(const Containers::String attribute = self.meshAttributeName(name))
return std::string{attribute};
return {};
}, "String name for given mesh attribute", py::arg("name"))
.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")

Loading…
Cancel
Save