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 .. py:property:: magnum.trade.ImageData3D.pixels
:raise AttributeError: If :ref:`is_compressed` is :py:`True` :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 .. py:property:: magnum.trade.MeshData.index_count
:raise AttributeError: If :ref:`is_indexed` is :py:`False` :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 .. py:class:: magnum.trade.ImporterManager
:summary: Manager for :ref:`AbstractImporter` plugin instances :summary: Manager for :ref:`AbstractImporter` plugin instances

4
src/python/magnum/__init__.py

@ -78,7 +78,9 @@ __all__ = [
'ImageView1D', 'ImageView2D', 'ImageView3D', 'ImageView1D', 'ImageView2D', 'ImageView3D',
'MutableImageView1D', 'MutableImageView2D', 'MutableImageView3D', 'MutableImageView1D', 'MutableImageView2D', 'MutableImageView3D',
'SamplerFilter', 'SamplerMipmap', 'SamplerWrapping' 'SamplerFilter', 'SamplerMipmap', 'SamplerWrapping',
'VertexFormat'
# TARGET_*, BUILD_* are omitted as `from magnum import *` would pull them # TARGET_*, BUILD_* are omitted as `from magnum import *` would pull them
# to globals and this would likely cause conflicts (corrade also defines # 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/PixelFormat.h>
#include <Magnum/PixelStorage.h> #include <Magnum/PixelStorage.h>
#include <Magnum/Sampler.h> #include <Magnum/Sampler.h>
#include <Magnum/VertexFormat.h>
#include "Corrade/PythonBindings.h" #include "Corrade/PythonBindings.h"
#include "Corrade/Containers/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_EDGE", SamplerWrapping::ClampToEdge)
.value("CLAMP_TO_BORDER", SamplerWrapping::ClampToBorder) .value("CLAMP_TO_BORDER", SamplerWrapping::ClampToBorder)
.value("MIRROR_CLAMP_TO_EDGE", SamplerWrapping::MirrorClampToEdge); .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() a = primitives.axis2d()
self.assertEqual(a.primitive, MeshPrimitive.LINES) self.assertEqual(a.primitive, MeshPrimitive.LINES)
self.assertTrue(a.is_indexed) self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 2) self.assertEqual(a.attribute_count(), 2)
def test_3d(self): def test_3d(self):
a = primitives.axis3d() a = primitives.axis3d()
self.assertEqual(a.primitive, MeshPrimitive.LINES) self.assertEqual(a.primitive, MeshPrimitive.LINES)
self.assertTrue(a.is_indexed) self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 2) self.assertEqual(a.attribute_count(), 2)
class Capsule(unittest.TestCase): class Capsule(unittest.TestCase):
def test_2d_wireframe(self): def test_2d_wireframe(self):
a = primitives.capsule2d_wireframe(3, 3, 2.0) a = primitives.capsule2d_wireframe(3, 3, 2.0)
self.assertEqual(a.primitive, MeshPrimitive.LINES) self.assertEqual(a.primitive, MeshPrimitive.LINES)
self.assertEqual(a.attribute_count, 1) self.assertEqual(a.attribute_count(), 1)
def test_3d_solid(self): def test_3d_solid(self):
a = primitives.capsule3d_solid(3, 3, 10, 2.0, primitives.CapsuleFlags.TEXTURE_COORDINATES|primitives.CapsuleFlags.TANGENTS) a = primitives.capsule3d_solid(3, 3, 10, 2.0, primitives.CapsuleFlags.TEXTURE_COORDINATES|primitives.CapsuleFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed) 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) b = primitives.capsule3d_solid(3, 3, 10, 2.0)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(b.is_indexed) self.assertTrue(b.is_indexed)
self.assertEqual(b.attribute_count, 2) self.assertEqual(b.attribute_count(), 2)
def test_3d_wireframe(self): def test_3d_wireframe(self):
a = primitives.capsule3d_wireframe(5, 3, 12, 0.3) 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) a = primitives.circle2d_solid(5, primitives.Circle2DFlags.TEXTURE_COORDINATES)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_FAN) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_FAN)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2) self.assertEqual(a.attribute_count(), 2)
b = primitives.circle2d_solid(5) b = primitives.circle2d_solid(5)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_FAN) self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_FAN)
self.assertFalse(b.is_indexed) self.assertFalse(b.is_indexed)
self.assertEqual(b.attribute_count, 1) self.assertEqual(b.attribute_count(), 1)
def test_2d_wireframe(self): def test_2d_wireframe(self):
a = primitives.circle2d_wireframe(5) 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) a = primitives.circle3d_solid(5, primitives.Circle3DFlags.TEXTURE_COORDINATES|primitives.Circle3DFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_FAN) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_FAN)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 4) self.assertEqual(a.attribute_count(), 4)
b = primitives.circle3d_solid(5) b = primitives.circle3d_solid(5)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_FAN) self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_FAN)
self.assertFalse(b.is_indexed) self.assertFalse(b.is_indexed)
self.assertEqual(b.attribute_count, 2) self.assertEqual(b.attribute_count(), 2)
def test_3d_wireframe(self): def test_3d_wireframe(self):
a = primitives.circle3d_wireframe(5) 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) a = primitives.cone_solid(5, 7, 7.1, primitives.ConeFlags.TEXTURE_COORDINATES|primitives.ConeFlags.CAP_END)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed) 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) b = primitives.cone_solid(5, 7, 7.1)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(b.is_indexed) self.assertTrue(b.is_indexed)
self.assertEqual(b.attribute_count, 2) self.assertEqual(b.attribute_count(), 2)
def test_wireframe(self): def test_wireframe(self):
a = primitives.cone_wireframe(16, 7.1) 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) a = primitives.cylinder_solid(7, 12, 0.2, primitives.CylinderFlags.TEXTURE_COORDINATES|primitives.CylinderFlags.CAP_ENDS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed) 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) b = primitives.cylinder_solid(7, 12, 0.2)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(b.is_indexed) self.assertTrue(b.is_indexed)
self.assertEqual(b.attribute_count, 2) self.assertEqual(b.attribute_count(), 2)
def test_wireframe(self): def test_wireframe(self):
a = primitives.cylinder_wireframe(8, 16, 1.1) 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()) a = primitives.gradient2d((3.1, 2.0), Color3(), (0.2, 1.1), Color4())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2) self.assertEqual(a.attribute_count(), 2)
def test_gradient2d_horizontal(self): def test_gradient2d_horizontal(self):
a = primitives.gradient2d_horizontal(Color4(), Color3()) a = primitives.gradient2d_horizontal(Color4(), Color3())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2) self.assertEqual(a.attribute_count(), 2)
def test_gradient2d_vertical(self): def test_gradient2d_vertical(self):
a = primitives.gradient2d_vertical(Color4(), Color3()) a = primitives.gradient2d_vertical(Color4(), Color3())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2) self.assertEqual(a.attribute_count(), 2)
def test_gradient3d(self): def test_gradient3d(self):
a = primitives.gradient3d((3.1, 2.0, 0.1), Color3(), (0.2, 1.1, 1.2), Color4()) 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.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 3) self.assertEqual(a.attribute_count(), 3)
def test_gradient3d_horizontal(self): def test_gradient3d_horizontal(self):
a = primitives.gradient3d_horizontal(Color4(), Color3()) a = primitives.gradient3d_horizontal(Color4(), Color3())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 3) self.assertEqual(a.attribute_count(), 3)
def test_gradient3d_vertical(self): def test_gradient3d_vertical(self):
a = primitives.gradient3d_vertical(Color4(), Color3()) a = primitives.gradient3d_vertical(Color4(), Color3())
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 3) self.assertEqual(a.attribute_count(), 3)
class Grid(unittest.TestCase): class Grid(unittest.TestCase):
def test_solid(self): def test_solid(self):
a = primitives.grid3d_solid((4, 5), primitives.GridFlags.NORMALS|primitives.GridFlags.TANGENTS) a = primitives.grid3d_solid((4, 5), primitives.GridFlags.NORMALS|primitives.GridFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed) self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 3) self.assertEqual(a.attribute_count(), 3)
def test_wireframe(self): def test_wireframe(self):
a = primitives.grid3d_wireframe((2, 7)) 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) a = primitives.plane_solid(primitives.PlaneFlags.TEXTURE_COORDINATES|primitives.PlaneFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 4) self.assertEqual(a.attribute_count(), 4)
b = primitives.plane_solid() b = primitives.plane_solid()
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_STRIP) self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(b.is_indexed) self.assertFalse(b.is_indexed)
self.assertEqual(b.attribute_count, 2) self.assertEqual(b.attribute_count(), 2)
def test_wireframe(self): def test_wireframe(self):
a = primitives.plane_wireframe() a = primitives.plane_wireframe()
@ -255,12 +255,12 @@ class Square(unittest.TestCase):
a = primitives.square_solid(primitives.SquareFlags.TEXTURE_COORDINATES) a = primitives.square_solid(primitives.SquareFlags.TEXTURE_COORDINATES)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(a.is_indexed) self.assertFalse(a.is_indexed)
self.assertEqual(a.attribute_count, 2) self.assertEqual(a.attribute_count(), 2)
b = primitives.square_solid() b = primitives.square_solid()
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_STRIP) self.assertEqual(b.primitive, MeshPrimitive.TRIANGLE_STRIP)
self.assertFalse(b.is_indexed) self.assertFalse(b.is_indexed)
self.assertEqual(b.attribute_count, 1) self.assertEqual(b.attribute_count(), 1)
def test_wireframe(self): def test_wireframe(self):
a = primitives.square_wireframe() 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) a = primitives.uv_sphere_solid(3, 7, primitives.UVSphereFlags.TEXTURE_COORDINATES|primitives.UVSphereFlags.TANGENTS)
self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(a.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(a.is_indexed) self.assertTrue(a.is_indexed)
self.assertEqual(a.attribute_count, 4) self.assertEqual(a.attribute_count(), 4)
b = primitives.uv_sphere_solid(3, 7) b = primitives.uv_sphere_solid(3, 7)
self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(b.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(b.is_indexed) self.assertTrue(b.is_indexed)
self.assertEqual(b.attribute_count, 2) self.assertEqual(b.attribute_count(), 2)
def test_wireframe(self): def test_wireframe(self):
a = primitives.uv_sphere_wireframe(6, 8) 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): class MeshData(unittest.TestCase):
def test(self): def test(self):
# The only way to get a mesh instance is through a manager
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.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) mesh_refcount = sys.getrefcount(mesh)
self.assertTrue(mesh.is_indexed)
self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) 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.index_count, 3)
self.assertEqual(mesh.attribute_count, 2) self.assertEqual(mesh.index_type, MeshIndexType.UNSIGNED_SHORT)
# TODO: test more, once it's exposed 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 index_data = mesh.index_data
self.assertEqual(len(index_data), 3) self.assertEqual(len(index_data), 6)
self.assertIs(index_data.owner, mesh) self.assertIs(index_data.owner, mesh)
self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1) self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1)
@ -123,7 +164,7 @@ class MeshData(unittest.TestCase):
self.assertEqual(sys.getrefcount(mesh), mesh_refcount) self.assertEqual(sys.getrefcount(mesh), mesh_refcount)
vertex_data = mesh.vertex_data vertex_data = mesh.vertex_data
self.assertEqual(len(vertex_data), 72) self.assertEqual(len(vertex_data), 84)
self.assertIs(vertex_data.owner, mesh) self.assertIs(vertex_data.owner, mesh)
self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1) self.assertEqual(sys.getrefcount(mesh), mesh_refcount + 1)
@ -131,11 +172,10 @@ class MeshData(unittest.TestCase):
self.assertEqual(sys.getrefcount(mesh), mesh_refcount) self.assertEqual(sys.getrefcount(mesh), mesh_refcount)
def test_nonindexed(self): def test_nonindexed(self):
# The only way to get a mesh instance is through a manager
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.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) self.assertFalse(mesh.is_indexed)
# Accessing the index data should be possible, they're just empty # 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 # Accessing any other index-related info should cause an exception
with self.assertRaisesRegex(AttributeError, "mesh is not indexed"): with self.assertRaisesRegex(AttributeError, "mesh is not indexed"):
mesh.index_count 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): class Importer(unittest.TestCase):
def test(self): def test(self):
@ -257,39 +347,52 @@ class Importer(unittest.TestCase):
def test_mesh(self): def test_mesh(self):
# importer refcounting tested in image2d # importer refcounting tested in image2d
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.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_level_count(0), 1)
self.assertEqual(importer.mesh_name(0), 'Non-indexed mesh') self.assertEqual(importer.mesh_name(0), 'Indexed mesh')
self.assertEqual(importer.mesh_for_name('Non-indexed mesh'), 0) 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) mesh = importer.mesh(0)
self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES)
self.assertTrue(mesh.has_attribute(importer.mesh_attribute_for_name("_CUSTOM_ATTRIBUTE")))
def test_mesh_level_oob(self): def test_mesh_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.glb')) importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
with self.assertRaises(IndexError): with self.assertRaises(IndexError):
importer.mesh(0, 1) importer.mesh(0, 1)
def test_mesh_by_name(self): def test_mesh_by_name(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.glb')) importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
mesh = importer.mesh('Non-indexed mesh') mesh = importer.mesh('Non-indexed mesh')
self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES) self.assertEqual(mesh.primitive, MeshPrimitive.TRIANGLES)
def test_mesh_by_name_not_found(self): def test_mesh_by_name_not_found(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.glb')) importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
with self.assertRaises(KeyError): with self.assertRaises(KeyError):
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.glb')) importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
with self.assertRaises(IndexError): with self.assertRaises(IndexError):
importer.mesh('Non-indexed mesh', 1) importer.mesh('Non-indexed mesh', 1)
@ -369,8 +472,8 @@ class ImageConverter(unittest.TestCase):
class SceneConverter(unittest.TestCase): class SceneConverter(unittest.TestCase):
def test_mesh(self): def test_mesh(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.glb')) importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
mesh = importer.mesh(0) mesh = importer.mesh(1)
converter = trade.SceneConverterManager().load_and_instantiate('StanfordSceneConverter') converter = trade.SceneConverterManager().load_and_instantiate('StanfordSceneConverter')
@ -380,8 +483,8 @@ class SceneConverter(unittest.TestCase):
def test_mesh_failed(self): def test_mesh_failed(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.glb')) importer.open_file(os.path.join(os.path.dirname(__file__), 'mesh.gltf'))
mesh = importer.mesh(0) mesh = importer.mesh(1)
converter = trade.SceneConverterManager().load_and_instantiate('AnySceneConverter') converter = trade.SceneConverterManager().load_and_instantiate('AnySceneConverter')

130
src/python/magnum/trade.cpp

@ -35,6 +35,7 @@
#include <Magnum/Trade/MeshData.h> #include <Magnum/Trade/MeshData.h>
#include "Corrade/Containers/PythonBindings.h" #include "Corrade/Containers/PythonBindings.h"
#include "Corrade/Containers/OptionalPythonBindings.h"
#include "Corrade/Containers/StridedArrayViewPythonBindings.h" #include "Corrade/Containers/StridedArrayViewPythonBindings.h"
#include "Magnum/PythonBindings.h" #include "Magnum/PythonBindings.h"
@ -305,11 +306,24 @@ void trade(py::module_& m) {
/* AbstractImporter depends on this */ /* AbstractImporter depends on this */
py::module_::import("corrade.pluginmanager"); 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"} py::class_<Trade::MeshData>{m, "MeshData", "Mesh data"}
.def_property_readonly("primitive", &Trade::MeshData::primitive, "Primitive") .def_property_readonly("primitive", &Trade::MeshData::primitive, "Primitive")
.def_property_readonly("index_data", [](Trade::MeshData& self) { .def_property_readonly("index_data", [](Trade::MeshData& self) {
return Containers::pyArrayViewHolder(self.indexData(), py::cast(self)); return Containers::pyArrayViewHolder(self.indexData(), py::cast(self));
}, "Raw index data") }, "Raw index data")
/** @todo direct access to MeshAttributeData, once making custom
MeshData is desired */
.def_property_readonly("vertex_data", [](Trade::MeshData& self) { .def_property_readonly("vertex_data", [](Trade::MeshData& self) {
return Containers::pyArrayViewHolder(self.vertexData(), py::cast(self)); return Containers::pyArrayViewHolder(self.vertexData(), py::cast(self));
}, "Raw vertex data") }, "Raw vertex data")
@ -319,11 +333,109 @@ void trade(py::module_& m) {
PyErr_SetString(PyExc_AttributeError, "mesh is not indexed"); PyErr_SetString(PyExc_AttributeError, "mesh is not indexed");
throw py::error_already_set{}; throw py::error_already_set{};
} }
return self.indexCount(); return self.indexCount();
}, "Index count") }, "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("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::ImageData1D> imageData1D{m, "ImageData1D", "One-dimensional image data"};
py::class_<Trade::ImageData2D> imageData2D{m, "ImageData2D", "Two-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_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", 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) .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("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")

Loading…
Cancel
Save