Browse Source

python/magum/math: switch to our own buffer protocol implementation.

Also don't needlessly define buffer protocol again for subclasses (such
as Matrix3 or Color4) as the base can handle that just fine.
pull/2/head
Vladimír Vondruš 7 years ago
parent
commit
d1d6cb9ec0
  1. 50
      src/python/magnum/math.cpp
  2. 35
      src/python/magnum/math.h
  3. 83
      src/python/magnum/math.matrix.h
  4. 9
      src/python/magnum/math.matrixdouble.cpp
  5. 9
      src/python/magnum/math.matrixfloat.cpp
  6. 121
      src/python/magnum/math.vector.h
  7. 9
      src/python/magnum/math.vectorfloat.cpp
  8. 56
      src/python/magnum/test/test_math.py

50
src/python/magnum/math.cpp

@ -37,6 +37,56 @@
namespace magnum {
/* Keep in sync with math.h */
const char* const FormatStrings[]{
/* 0. Representing bytes as unsigned. Not using 'c' because then it behaves
differently from bytes/bytearray, where you can do `a[0] = ord('A')`. */
"B",
"b", /* 1 -- std::int8_t */
"B", /* 2 -- std::uint8_t */
"i", /* 3 -- std::int32_t */
"I", /* 4 -- std::uint32_t */
"f", /* 5 -- float */
"d" /* 6 -- double */
};
/* Flipped as numpy expects row-major */
const Py_ssize_t MatrixShapes[][2]{
{2, 2}, /* 0 -- 2 cols, 2 rows */
{3, 2}, /* 1 -- 2 cols, 3 rows */
{4, 2}, /* 2 -- 2 cols, 4 rows */
{2, 3}, /* 3 -- 3 cols, 2 rows */
{3, 3}, /* 4 -- 3 cols, 3 rows */
{4, 3}, /* 5 -- 3 cols, 4 rows */
{2, 4}, /* 6 -- 4 cols, 2 rows */
{3, 4}, /* 7 -- 4 cols, 3 rows */
{4, 4} /* 8 -- 4 cols, 4 rows */
};
const Py_ssize_t MatrixStridesFloat[][2]{
{4, 4*2}, /* 0 -- 2 cols, 2 rows */
{4, 4*3}, /* 1 -- 2 cols, 3 rows */
{4, 4*4}, /* 2 -- 2 cols, 4 rows */
{4, 4*2}, /* 3 -- 3 cols, 2 rows */
{4, 4*3}, /* 4 -- 3 cols, 3 rows */
{4, 4*4}, /* 5 -- 3 cols, 4 rows */
{4, 4*2}, /* 6 -- 4 cols, 2 rows */
{4, 4*3}, /* 7 -- 4 cols, 3 rows */
{4, 4*4} /* 8 -- 4 cols, 4 rows */
};
const Py_ssize_t MatrixStridesDouble[][2]{
{8, 8*2}, /* 0 -- 2 cols, 2 rows */
{8, 8*3}, /* 1 -- 2 cols, 3 rows */
{8, 8*4}, /* 2 -- 2 cols, 4 rows */
{8, 8*2}, /* 3 -- 3 cols, 2 rows */
{8, 8*3}, /* 4 -- 3 cols, 3 rows */
{8, 8*4}, /* 5 -- 3 cols, 4 rows */
{8, 8*2}, /* 6 -- 4 cols, 2 rows */
{8, 8*3}, /* 7 -- 4 cols, 3 rows */
{8, 8*4} /* 8 -- 4 cols, 4 rows */
};
namespace {
template<class T> void angle(py::class_<T>& c) {

35
src/python/magnum/math.h

@ -26,6 +26,7 @@
*/
#include <sstream>
#include <Python.h>
#include <Corrade/Utility/Debug.h>
#include <Magnum/Magnum.h>
@ -33,6 +34,40 @@
namespace magnum {
/* Keep in sync with math.cpp */
extern const char* const FormatStrings[];
template<class> constexpr std::size_t formatIndex();
template<> constexpr std::size_t formatIndex<char>() { return 0; }
template<> constexpr std::size_t formatIndex<Byte>() { return 1; }
template<> constexpr std::size_t formatIndex<UnsignedByte>() { return 2; }
template<> constexpr std::size_t formatIndex<Int>() { return 3; }
template<> constexpr std::size_t formatIndex<UnsignedInt>() { return 4; }
template<> constexpr std::size_t formatIndex<Float>() { return 5; }
template<> constexpr std::size_t formatIndex<Double>() { return 6; }
extern const Py_ssize_t MatrixShapes[][2];
template<UnsignedInt cols, UnsignedInt rows> constexpr std::size_t matrixShapeStrideIndex();
template<> constexpr std::size_t matrixShapeStrideIndex<2, 2>() { return 0; }
template<> constexpr std::size_t matrixShapeStrideIndex<2, 3>() { return 1; }
template<> constexpr std::size_t matrixShapeStrideIndex<2, 4>() { return 2; }
template<> constexpr std::size_t matrixShapeStrideIndex<3, 2>() { return 3; }
template<> constexpr std::size_t matrixShapeStrideIndex<3, 3>() { return 4; }
template<> constexpr std::size_t matrixShapeStrideIndex<3, 4>() { return 5; }
template<> constexpr std::size_t matrixShapeStrideIndex<4, 2>() { return 6; }
template<> constexpr std::size_t matrixShapeStrideIndex<4, 3>() { return 7; }
template<> constexpr std::size_t matrixShapeStrideIndex<4, 4>() { return 8; }
extern const Py_ssize_t MatrixStridesFloat[][2];
extern const Py_ssize_t MatrixStridesDouble[][2];
template<class> constexpr const Py_ssize_t* matrixStridesFor(std::size_t i);
template<> constexpr const Py_ssize_t* matrixStridesFor<Float>(std::size_t i) {
return MatrixStridesFloat[i];
}
template<> constexpr const Py_ssize_t* matrixStridesFor<Double>(std::size_t i) {
return MatrixStridesDouble[i];
}
template<class T> std::string repr(const T& value) {
std::ostringstream out;
Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << value;

83
src/python/magnum/math.matrix.h

@ -27,11 +27,13 @@
#include <pybind11/pybind11.h>
#include <pybind11/operators.h>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/FormatStl.h>
#include <Magnum/Math/Matrix3.h>
#include <Magnum/Math/Matrix4.h>
#include "corrade/PybindExtras.h"
#include "corrade/PyBuffer.h"
#include "magnum/math.h"
@ -44,10 +46,10 @@ template<class T> struct VectorTraits<2, T> { typedef Math::Vector2<T> Type; };
template<class T> struct VectorTraits<3, T> { typedef Math::Vector3<T> Type; };
template<class T> struct VectorTraits<4, T> { typedef Math::Vector4<T> Type; };
template<class U, class T> void initFromBuffer(T& out, const py::buffer_info& info) {
template<class U, class T> void initFromBuffer(T& out, const Py_buffer& buffer) {
for(std::size_t i = 0; i != T::Cols; ++i)
for(std::size_t j = 0; j != T::Rows; ++j)
out[i][j] = static_cast<typename T::Type>(*reinterpret_cast<const U*>(static_cast<const char*>(info.ptr) + i*info.strides[1] + j*info.strides[0]));
out[i][j] = static_cast<typename T::Type>(*reinterpret_cast<const U*>(static_cast<const char*>(buffer.buf) + i*buffer.strides[1] + j*buffer.strides[0]));
}
/* Called for both Matrix3x3 and Matrix3 in order to return a proper type /
@ -67,24 +69,30 @@ template<class T, class ...Args> void everyRectangularMatrix(py::class_<T, Args.
.def(py::init(), "Default constructor")
.def(py::init<typename T::Type>(), "Construct a matrix with one value for all components")
/* Buffer protocol, needed in order to make numpy treat the matric
correctly as column-major. Has to be defined *before* the from-tuple
constructor so it gets precedence for types that implement the
buffer protocol. */
.def(py::init([](py::buffer buffer) {
py::buffer_info info = buffer.request();
/* Buffer protocol, needed in order to properly detect row-major
layouts. Has to be defined *before* the from-tuple constructor so it
gets precedence for types that implement the buffer protocol. */
.def(py::init([](py::buffer other) {
Py_buffer buffer{};
if(PyObject_GetBuffer(other.ptr(), &buffer, PyBUF_FORMAT|PyBUF_STRIDES) != 0)
throw py::error_already_set{};
if(info.ndim != 2)
throw py::buffer_error{Utility::formatString("expected 2 dimensions but got {}", info.ndim)};
Containers::ScopeGuard e{&buffer, PyBuffer_Release};
if(info.shape[0] != T::Rows ||info.shape[1] != T::Cols)
throw py::buffer_error{Utility::formatString("expected {}x{} elements but got {}x{}", T::Cols, T::Rows, info.shape[1], info.shape[0])};
if(buffer.ndim != 2)
throw py::buffer_error{Utility::formatString("expected 2 dimensions but got {}", buffer.ndim)};
if(buffer.shape[0] != T::Rows || buffer.shape[1] != T::Cols)
throw py::buffer_error{Utility::formatString("expected {}x{} elements but got {}x{}", T::Cols, T::Rows, buffer.shape[1], buffer.shape[0])};
T out{Math::NoInit};
if(info.format == "f") initFromBuffer<Float>(out, info);
else if(info.format == "d") initFromBuffer<Double>(out, info);
else throw py::buffer_error{Utility::formatString("expected format f or d but got {}", info.format)};
/* Expecting just an one-letter format */
if(buffer.format[0] == 'f' && !buffer.format[1])
initFromBuffer<Float>(out, buffer);
else if(buffer.format[0] == 'd' && !buffer.format[1])
initFromBuffer<Double>(out, buffer);
else throw py::buffer_error{Utility::formatString("expected format f or d but got {}", buffer.format)};
return out;
}), "Construct from a buffer")
@ -113,6 +121,31 @@ template<class T, class ...Args> void everyRectangularMatrix(py::class_<T, Args.
}, "Values on diagonal");
}
template<class T> bool rectangularMatrixBufferProtocol(T& self, Py_buffer& buffer, int flags) {
/* I hate the const_casts but I assume this is to make editing easier, NOT
to make it possible for users to stomp on these values. */
buffer.ndim = 2;
buffer.itemsize = sizeof(typename T::Type);
buffer.len = sizeof(T);
buffer.buf = self.data();
buffer.readonly = false;
if((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
buffer.format = const_cast<char*>(FormatStrings[formatIndex<typename T::Type>()]);
if(flags != PyBUF_SIMPLE) {
/* Reusing shape definitions from matrices because I don't want to
create another useless array for that and reinterpret_cast on the
buffer.internal is UGLY. It's flipped from column-major to
row-major, so adjusting the row instead. */
buffer.shape = const_cast<Py_ssize_t*>(MatrixShapes[matrixShapeStrideIndex<T::Cols, T::Rows>()]);
CORRADE_INTERNAL_ASSERT(buffer.shape[0] == T::Rows);
CORRADE_INTERNAL_ASSERT(buffer.shape[1] == T::Cols);
if((flags & PyBUF_STRIDES) == PyBUF_STRIDES)
buffer.strides = const_cast<Py_ssize_t*>(matrixStridesFor<typename T::Type>(matrixShapeStrideIndex<T::Cols, T::Rows>()));
}
return true;
}
template<class T> void rectangularMatrix(py::class_<T>& c) {
/*
Missing APIs:
@ -128,21 +161,6 @@ template<class T> void rectangularMatrix(py::class_<T>& c) {
*/
c
/* Buffer protocol, needed in order to make numpy treat the matrix
correctly as column-major. The constructor is defined in
everyRectangularMatrix(). */
.def_buffer([](const T& self) -> py::buffer_info {
// TODO: ownership?
return py::buffer_info{
const_cast<typename T::Type*>(self.data()),
sizeof(typename T::Type),
py::format_descriptor<typename T::Type>::format(),
2,
{T::Rows, T::Cols},
{sizeof(typename T::Type), sizeof(typename T::Type)*T::Rows}
};
})
/* Comparison */
.def(py::self == py::self, "Equality comparison")
.def(py::self != py::self, "Non-equality comparison")
@ -170,6 +188,11 @@ template<class T> void rectangularMatrix(py::class_<T>& c) {
.def("__repr__", repr<T>, "Object representation");
/* Buffer protocol, needed in order to make numpy treat the matrix
correctly as column-major. The constructor is defined in
everyRectangularMatrix(). */
corrade::enableBetterBufferProtocol<T, rectangularMatrixBufferProtocol>(c);
/* Matrix column count */
char lenDocstring[] = "Matrix column count. Returns _.";
lenDocstring[sizeof(lenDocstring) - 3] = '0' + T::Cols;

9
src/python/magnum/math.matrixdouble.cpp

@ -40,8 +40,13 @@ void mathMatrixDouble(py::module& root) {
py::class_<Matrix4x3d> matrix4x3d{root, "Matrix4x3d", "4x3 double matrix", py::buffer_protocol{}};
py::class_<Matrix4x4d> matrix4x4d{root, "Matrix4x4d", "4x4 double matrix", py::buffer_protocol{}};
py::class_<Matrix3d, Matrix3x3d> matrix3d{root, "Matrix3d", "2D double transformation matrix", py::buffer_protocol{}};
py::class_<Matrix4d, Matrix4x4d> matrix4d{root, "Matrix4d", "3D double transformation matrix", py::buffer_protocol{}};
/* The subclasses don't have buffer protocol enabled, as that's already
done by the base classes. Moreover, just adding py::buffer_protocol{}
would cause it to not find the buffer functions as we don't add them
anywhere, thus failing with `pybind11_getbuffer(): Internal error`. */
py::class_<Matrix3d, Matrix3x3d> matrix3d{root, "Matrix3d", "2D double transformation matrix"};
py::class_<Matrix4d, Matrix4x4d> matrix4d{root, "Matrix4d", "3D double transformation matrix"};
matrices<Double>(
matrix2x2d, matrix2x3d, matrix2x4d,

9
src/python/magnum/math.matrixfloat.cpp

@ -40,8 +40,13 @@ void mathMatrixFloat(py::module& root) {
py::class_<Matrix4x3> matrix4x3{root, "Matrix4x3", "4x3 float matrix", py::buffer_protocol{}};
py::class_<Matrix4x4> matrix4x4{root, "Matrix4x4", "4x4 float matrix", py::buffer_protocol{}};
py::class_<Matrix3, Matrix3x3> matrix3{root, "Matrix3", "2D float transformation matrix", py::buffer_protocol{}};
py::class_<Matrix4, Matrix4x4> matrix4{root, "Matrix4", "3D float transformation matrix", py::buffer_protocol{}};
/* The subclasses don't have buffer protocol enabled, as that's already
done by the base classes. Moreover, just adding py::buffer_protocol{}
would cause it to not find the buffer functions as we don't add them
anywhere, thus failing with `pybind11_getbuffer(): Internal error`. */
py::class_<Matrix3, Matrix3x3> matrix3{root, "Matrix3", "2D float transformation matrix"};
py::class_<Matrix4, Matrix4x4> matrix4{root, "Matrix4", "3D float transformation matrix"};
matrices<Float>(
matrix2x2, matrix2x3, matrix2x4,

121
src/python/magnum/math.vector.h

@ -26,53 +26,55 @@
*/
#include <pybind11/operators.h>
#include <Corrade/Containers/ScopeGuard.h>
#include <Corrade/Utility/FormatStl.h>
#include <Magnum/Math/Color.h>
#include <Magnum/Math/Vector4.h>
#include "corrade/PybindExtras.h"
#include "corrade/PyBuffer.h"
#include "magnum/math.h"
namespace magnum {
template<class> bool isTypeCompatible(const std::string&);
template<> inline bool isTypeCompatible<Float>(const std::string& format) {
return format == "f" || format == "d";
template<class> constexpr bool isTypeCompatible(char);
template<> constexpr bool isTypeCompatible<Float>(char format) {
return format == 'f' || format == 'd';
}
template<> inline bool isTypeCompatible<Double>(const std::string& format) {
return format == "f" || format == "d";
template<> constexpr bool isTypeCompatible<Double>(char format) {
return format == 'f' || format == 'd';
}
template<> inline bool isTypeCompatible<Int>(const std::string& format) {
return format == "i" || format == "l";
template<> constexpr bool isTypeCompatible<Int>(char format) {
return format == 'i' || format == 'l';
}
template<> inline bool isTypeCompatible<UnsignedInt>(const std::string& format) {
return format == "I" || format == "L";
template<> constexpr bool isTypeCompatible<UnsignedInt>(char format) {
return format == 'I' || format == 'L';
}
template<class U, class T> void initFromBuffer(T& out, const py::buffer_info& info) {
template<class U, class T> void initFromBuffer(T& out, const Py_buffer& buffer) {
for(std::size_t i = 0; i != T::Size; ++i)
out[i] = static_cast<typename T::Type>(*reinterpret_cast<const U*>(static_cast<const char*>(info.ptr) + i*info.strides[0]));
out[i] = static_cast<typename T::Type>(*reinterpret_cast<const U*>(static_cast<const char*>(buffer.buf) + i*buffer.strides[0]));
}
/* Floating-point init */
template<class T> void initFromBuffer(T& out, const py::buffer_info& info, std::true_type, std::true_type) {
if(info.format == "f") initFromBuffer<Float>(out, info);
else if(info.format == "d") initFromBuffer<Double>(out, info);
template<class T> void initFromBuffer(typename std::enable_if<std::is_floating_point<typename T::Type>::value, T>::type& out, const Py_buffer& buffer) {
if(buffer.format[0] == 'f') initFromBuffer<Float>(out, buffer);
else if(buffer.format[0] == 'd') initFromBuffer<Double>(out, buffer);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
/* Signed integeral init */
template<class T> void initFromBuffer(T& out, const py::buffer_info& info, std::false_type, std::true_type) {
if(info.format == "i") initFromBuffer<Int>(out, info);
else if(info.format == "l") initFromBuffer<Long>(out, info);
/* Signed integral init */
template<class T> void initFromBuffer(typename std::enable_if<std::is_integral<typename T::Type>::value && std::is_signed<typename T::Type>::value, T>::type& out, const Py_buffer& buffer) {
if(buffer.format[0] == 'i') initFromBuffer<Int>(out, buffer);
else if(buffer.format[0] == 'l') initFromBuffer<Long>(out, buffer);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
/* Unsigned integeral init */
template<class T> void initFromBuffer(T& out, const py::buffer_info& info, std::false_type, std::false_type) {
if(info.format == "I") initFromBuffer<UnsignedInt>(out, info);
else if(info.format == "L") initFromBuffer<UnsignedLong>(out, info);
/* Unsigned integral init */
template<class T> void initFromBuffer(typename std::enable_if<std::is_integral<typename T::Type>::value && std::is_unsigned<typename T::Type>::value, T>::type& out, const Py_buffer& buffer) {
if(buffer.format[0] == 'I') initFromBuffer<UnsignedInt>(out, buffer);
else if(buffer.format[0] == 'L') initFromBuffer<UnsignedLong>(out, buffer);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}
@ -91,25 +93,6 @@ template<class T, class ...Args> void everyVector(py::class_<T, Args...>& c) {
}, "Construct a zero vector")
.def(py::init(), "Default constructor")
/* Ideally, only the constructor (in vectorBuffer()) would be needed
(and thus also no py::buffer_protocol() specified for the class),
but conversion of vectors to lists is extremely slow due to pybind
exceptions being somehow extra heavy compared to native python ones,
so in order to have acceptable performance we need the buffer
protocol on the other side as well. See test/benchmark_math.py for
more information. */
.def_buffer([](const T& self) -> py::buffer_info {
// TODO: ownership?
return py::buffer_info{
const_cast<typename T::Type*>(self.data()),
sizeof(typename T::Type),
py::format_descriptor<typename T::Type>::format(),
1,
{T::Size},
{sizeof(typename T::Type)}
};
})
/* Operators */
.def(-py::self, "Negated vector")
.def(py::self += py::self, "Add and assign a vector")
@ -135,24 +118,53 @@ template<class T, class ...Args> void vectorBuffer(py::class_<T, Args...>& c) {
/* Buffer protocol. If not present, implicit conversion from numpy
arrays of non-default types somehow doesn't work. There's also the
other part in vectorBuffer(). */
.def(py::init([](py::buffer buffer) {
py::buffer_info info = buffer.request();
.def(py::init([](py::buffer other) {
Py_buffer buffer{};
if(PyObject_GetBuffer(other.ptr(), &buffer, PyBUF_FORMAT|PyBUF_STRIDES) != 0)
throw py::error_already_set{};
Containers::ScopeGuard e{&buffer, PyBuffer_Release};
if(info.ndim != 1)
throw py::buffer_error{Utility::formatString("expected 1 dimension but got {}", info.ndim)};
if(buffer.ndim != 1)
throw py::buffer_error{Utility::formatString("expected 1 dimension but got {}", buffer.ndim)};
if(info.shape[0] != T::Size)
throw py::buffer_error{Utility::formatString("expected {} elements but got {}", T::Size, info.shape[0])};
if(buffer.shape[0] != T::Size)
throw py::buffer_error{Utility::formatString("expected {} elements but got {}", T::Size, buffer.shape[0])};
if(!isTypeCompatible<typename T::Type>(info.format))
throw py::buffer_error{Utility::formatString("unexpected format {} for a {} vector", info.format, py::format_descriptor<typename T::Type>::format())};
/* Expecting just an one-letter format */
if(!buffer.format[0] || buffer.format[1] || !isTypeCompatible<typename T::Type>(buffer.format[0]))
throw py::buffer_error{Utility::formatString("unexpected format {} for a {} vector", buffer.format, FormatStrings[formatIndex<typename T::Type>()])};
T out{Math::NoInit};
initFromBuffer(out, info, std::is_floating_point<typename T::Type>{}, std::is_signed<typename T::Type>{});
initFromBuffer<T>(out, buffer);
return out;
}), "Construct from a buffer");
}
template<class T> bool vectorBufferProtocol(T& self, Py_buffer& buffer, int flags) {
/* I hate the const_casts but I assume this is to make editing easier, NOT
to make it possible for users to stomp on these values. */
buffer.ndim = 1;
buffer.itemsize = sizeof(typename T::Type);
buffer.len = sizeof(T);
buffer.buf = self.data();
buffer.readonly = false;
if((flags & PyBUF_FORMAT) == PyBUF_FORMAT)
buffer.format = const_cast<char*>(FormatStrings[formatIndex<typename T::Type>()]);
if(flags != PyBUF_SIMPLE) {
/* Reusing shape definitions from matrices because I don't want to
create another useless array for that and reinterpret_cast on the
buffer.internal is UGLY. It's flipped from column-major to
row-major, so adjusting the row instead. */
buffer.shape = const_cast<Py_ssize_t*>(MatrixShapes[matrixShapeStrideIndex<2, T::Size>()]);
CORRADE_INTERNAL_ASSERT(buffer.shape[0] == T::Size);
if((flags & PyBUF_STRIDES) == PyBUF_STRIDES)
buffer.strides = &buffer.itemsize;
}
return true;
}
/* Things common for vectors of all sizes and types */
template<class T> void vector(py::module& m, py::class_<T>& c) {
/*
@ -203,6 +215,15 @@ template<class T> void vector(py::module& m, py::class_<T>& c) {
.def("__repr__", repr<T>, "Object representation");
/* Ideally, only the constructor (in vectorBuffer()) would be needed
(and thus also no py::buffer_protocol() specified for the class),
but conversion of vectors to lists is extremely slow due to pybind
exceptions being somehow extra heavy compared to native python ones,
so in order to have acceptable performance we need the buffer
protocol on the other side as well. See test/benchmark_math.py for more
information. */
corrade::enableBetterBufferProtocol<T, vectorBufferProtocol>(c);
/* Vector length */
char lenDocstring[] = "Vector size. Returns _.";
lenDocstring[sizeof(lenDocstring) - 3] = '0' + T::Size;

9
src/python/magnum/math.vectorfloat.cpp

@ -89,12 +89,17 @@ void mathVectorFloat(py::module& root, py::module& m) {
vectorsFloat<Float>(m, vector2, vector3, vector4);
vectorsFloat<Double>(m, vector2d, vector3d, vector4d);
py::class_<Color3, Vector3> color3_{root, "Color3", "Color in linear RGB color space", py::buffer_protocol{}};
/* The subclasses don't have buffer protocol enabled, as that's already
done by the base classes. Moreover, just adding py::buffer_protocol{}
would cause it to not find the buffer functions as we don't add them
anywhere, thus failing with `pybind11_getbuffer(): Internal error`. */
py::class_<Color3, Vector3> color3_{root, "Color3", "Color in linear RGB color space"};
everyVector(color3_);
color(color3_);
color3(color3_);
py::class_<Color4, Vector4> color4_{root, "Color4", "Color in linear RGBA color space", py::buffer_protocol{}};
py::class_<Color4, Vector4> color4_{root, "Color4", "Color in linear RGBA color space"};
everyVector(color4_);
color(color4_);
color4(color4_);

56
src/python/magnum/test/test_math.py

@ -23,6 +23,7 @@
# DEALINGS IN THE SOFTWARE.
#
import array
import unittest
from magnum import *
@ -263,6 +264,14 @@ class Vector(unittest.TestCase):
def test_repr(self):
self.assertEqual(repr(Vector3(1.0, 3.14, -13.37)), 'Vector(1, 3.14, -13.37)')
def test_from_buffer(self):
a = Vector3i(array.array('i', [2, 3, 5]))
self.assertEqual(a, Vector3i(2, 3, 5))
def test_to_buffer(self):
a = memoryview(Vector4(1.0, 2.0, 3.0, 4.0))
self.assertEqual(a.tolist(), [1.0, 2.0, 3.0, 4.0])
class Color3_(unittest.TestCase):
def test_init(self):
a1 = Color3()
@ -350,6 +359,16 @@ class Color4_(unittest.TestCase):
self.assertIsInstance(Color4().rgb, Color3)
self.assertIsInstance(Color4().xyz, Color3)
def test_from_buffer(self):
a = Color3(array.array('f', [2.0, 3.0, 5.0]))
self.assertEqual(a, Color3(2.0, 3.0, 5.0))
def test_to_buffer(self):
# Color4 doesn't define py::buffer_protocol(), the one from base should
# "just work"
a = memoryview(Color4(1.0, 2.0, 3.0, 4.0))
self.assertEqual(a.tolist(), [1.0, 2.0, 3.0, 4.0])
class Matrix(unittest.TestCase):
def test_init(self):
a = Matrix3x2()
@ -582,6 +601,13 @@ class Matrix(unittest.TestCase):
' 2, 5,\n'
' 3, 6)')
# conversion from buffer is tested in test_math_numpy, array.array is
# one-dimensional and I don't want to drag numpy here just for one test
def test_to_buffer(self):
a = memoryview(Matrix2x2((1.0, 2.0), (3.0, 4.0)))
self.assertEqual(a.tolist(), [[1.0, 3.0], [2.0, 4.0]])
class Matrix3_(unittest.TestCase):
def test_init(self):
a = Matrix3()
@ -673,6 +699,20 @@ class Matrix3_(unittest.TestCase):
self.assertIsInstance(Matrix3().transposed(), Matrix3)
self.assertIsInstance(Matrix3().inverted(), Matrix3)
# conversion from buffer is tested in test_math_numpy, array.array is
# one-dimensional and I don't want to drag numpy here just for one test
def test_to_buffer(self):
# Matrix3 doesn't define py::buffer_protocol(), the one from base
# should "just work"
a = memoryview(Matrix3((1.0, 2.0, 3.0),
(4.0, 5.0, 6.0),
(7.0, 8.0, 9.0)))
self.assertEqual(a.tolist(), [
[1.0, 4.0, 7.0],
[2.0, 5.0, 8.0],
[3.0, 6.0, 9.0]])
class Matrix4_(unittest.TestCase):
def test_init(self):
a = Matrix4()
@ -778,6 +818,22 @@ class Matrix4_(unittest.TestCase):
self.assertIsInstance(Matrix4().transposed(), Matrix4)
self.assertIsInstance(Matrix4().inverted(), Matrix4)
# conversion from buffer is tested in test_math_numpy, array.array is
# one-dimensional and I don't want to drag numpy here just for one test
def test_to_buffer(self):
# Matrix3 doesn't define py::buffer_protocol(), the one from base
# should "just work"
a = memoryview(Matrix4((1.0, 2.0, 3.0, 4.0),
(5.0, 6.0, 7.0, 8.0),
(9.0, 10.0, 11.0, 12.0),
(13.0, 14.0, 15.0, 16.0)))
self.assertEqual(a.tolist(), [
[1.0, 5.0, 9.0, 13.0],
[2.0, 6.0, 10.0, 14.0],
[3.0, 7.0, 11.0, 15.0],
[4.0, 8.0, 12.0, 16.0]])
class Quaternion_(unittest.TestCase):
def test_init(self):
a = Quaternion()

Loading…
Cancel
Save