Browse Source

python: finish the magic and docs for Matrix[34].scaling() and friends.

This is so ugly it's beautiful. The translation needed a metaclass to
work properly, but the undoubtedly worst/best is making those exposed
nicely in the docs.
pull/8/head
Vladimír Vondruš 7 years ago
parent
commit
14f7810870
  1. 21
      doc/python/magnum.math.rst
  2. 6
      src/python/magnum/bootstrap.h
  3. 71
      src/python/magnum/math.cpp
  4. 313
      src/python/magnum/math.matrix.h
  5. 10
      src/python/magnum/math.matrixdouble.cpp
  6. 12
      src/python/magnum/math.matrixfloat.cpp
  7. 12
      src/python/magnum/test/test_math.py
  8. 4
      src/python/magnum/test/test_math_numpy.py

21
doc/python/magnum.math.rst

@ -207,17 +207,14 @@
implemented as a *real* swizzle, allowing for convenient expressions
like :py:`vec.xz = (3.5, 0.1)`.
`Static constructors and instance method overloads`_
----------------------------------------------------
`Static constructors and instance method / property overloads`_
---------------------------------------------------------------
While not common in Python, the `Matrix4.scaling()` / `Matrix4.rotation()`
methods mimic the C++ equivalent --- calling `Matrix4.scaling()` will
return a scaling matrix, while :py:`mat.scaling()` returns the 3x3 scaling
part of the matrix. Similarly for the `Matrix3` class.
.. block-warning:: Subject to change
On the other hand, there's currently just `Matrix3.translation()` and
the corresponding :py:`mat.translation` property is temporarily
available as an underscored `Matrix3._translation`. This will change
later.
methods mimic the C++ equivalent --- calling :py:`Matrix4.scaling(vec)`
will return a scaling matrix, while :py:`mat.scaling()` returns the 3x3
scaling part of the matrix. With `Matrix4.translation`, it's a bit more
involved --- calling :py:`Matrix4.translation(vec)` will return a
translation matrix, while :py:`mat.translation` is a read-write property
accessing the fourth column of the matrix. Similarly for the `Matrix3`
class.

6
src/python/magnum/bootstrap.h

@ -25,6 +25,8 @@
DEALINGS IN THE SOFTWARE.
*/
#include <Python.h>
namespace pybind11 { class module; }
namespace Magnum {}
@ -36,8 +38,8 @@ namespace py = pybind11;
void math(py::module& root, py::module& m);
void mathVectorFloat(py::module& root, py::module& m);
void mathVectorIntegral(py::module& root, py::module& m);
void mathMatrixFloat(py::module& root);
void mathMatrixDouble(py::module& root);
void mathMatrixFloat(py::module& root, PyTypeObject* metaclass);
void mathMatrixDouble(py::module& root, PyTypeObject* metaclass);
void mathRange(py::module& root, py::module& m);
void gl(py::module& m);

71
src/python/magnum/math.cpp

@ -341,6 +341,67 @@ template<class T> void quaternion(py::module& m, py::class_<T>& c) {
.def("__repr__", repr<T>, "Object representation");
}
/* Behaves exactly like Py_Type_Type.tp_getattro but redirects access to the
translation attribute to _stranslation in order to make it behave like a
function when called on an object */
PyObject* transformationMatrixGettattro(PyObject* const obj, PyObject* const name) {
if(PyUnicode_Check(name) && PyUnicode_CompareWithASCIIString(name, "translation") == 0) {
/* TODO: this means one allocation per every attribute access, any
chance we could minimize that? Storing a global reference to this
is crappy :/ Maybe allocate and store this inside
transformationMatrixMetaclass? But who would be responsible for
Py_DECREF then? Pybind's module destructors are kinda overdone:
https://pybind11.readthedocs.io/en/stable/advanced/misc.html#module-destructors */
PyObject* const _stranslation = PyUnicode_FromString("_stranslation");
PyObject* const ret = PyType_Type.tp_getattro(obj, _stranslation);
Py_DECREF(_stranslation);
return ret;
}
return PyType_Type.tp_getattro(obj, name);
}
/* Based off pybind11:detail::make_default_metaclass(), but with Python < 3.3
support and unneeded pybind specifics removed. In particular, we don't need
any static attribute access modifications from pybind's own metaclass, as
Matrix[34] doesn't need to support assignment to static attributes. */
PyTypeObject* transformationMatrixMetaclass() {
constexpr auto *name = "TransformationMatrixType";
auto name_obj = py::reinterpret_steal<py::object>(PyUnicode_FromString(name));
/* Danger zone: from now (and until PyType_Ready), make sure to
issue no Python C API calls which could potentially invoke the
garbage collector (the GC will call type_traverse(), which will in
turn find the newly constructed type in an invalid state) */
auto heap_type = reinterpret_cast<PyHeapTypeObject*>(PyType_Type.tp_alloc(&PyType_Type, 0));
if(!heap_type)
py::pybind11_fail("magnum::transformationMatrixMetaclass(): error allocating metaclass!");
heap_type->ht_name = name_obj.inc_ref().ptr();
heap_type->ht_qualname = name_obj.inc_ref().ptr();
auto type = &heap_type->ht_type;
type->tp_name = name;
type->tp_base = py::detail::type_incref(&PyType_Type);
type->tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE|Py_TPFLAGS_HEAPTYPE;
type->tp_setattro = PyType_Type.tp_setattro;
/* In order to create reasonable docs for this, we can't override the
translation attribute at that time --- the _stranslation will be then
used for documentation. */
if(std::getenv("MCSS_GENERATING_OUTPUT"))
type->tp_getattro = PyType_Type.tp_getattro;
else
type->tp_getattro = transformationMatrixGettattro;
if(PyType_Ready(type) < 0)
py::pybind11_fail("magnum::transformationMatrixMetaclass(): failure in PyType_Ready()!");
py::setattr(reinterpret_cast<PyObject*>(type), "__module__", py::str("magnum_builtins"));
return type;
}
}
void math(py::module& root, py::module& m) {
@ -391,9 +452,15 @@ void math(py::module& root, py::module& m) {
.def("acos", [](Double angle) { return Math::acos(angle); }, "Arc cosine")
.def("atan", [](Double angle) { return Math::atan(angle); }, "Arc tangent");
/* These are needed for the quaternion, so register them before */
/* These are needed for the quaternion, so register them before. Double
versions are called from inside these. */
magnum::mathVectorFloat(root, m);
magnum::mathMatrixFloat(root);
/* Matrices need a metaclass in order to support the magic translation
attribute, so allocate it here, just once. TODO: I'm not sure who's
responsible for deleting the object, actually -- however neither pybind
seems to be destructing the metaclasses in any way, so in the worst case
it's being done wrong in a consistent way. */
magnum::mathMatrixFloat(root, transformationMatrixMetaclass());
/* Quaternion */
py::class_<Quaternion> quaternion_(root, "Quaternion", "Float quaternion");

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

@ -531,17 +531,14 @@ template<class T> void matrices(
return self*other;
}, "Multiply a matrix");
/* 3x3 transformation matrix. Buffer constructors need to be *before* tuple
constructors so numpy buffer protocol gets extracted correctly. */
py::implicitly_convertible<Math::Matrix3x3<T>, Math::Matrix3<T>>();
everyRectangularMatrix(matrix3);
everyMatrix(matrix3);
matrix3
/* Constructors. The scaling() / rotation() are handled below
as they conflict with member functions. */
.def_static("translation", static_cast<Math::Matrix3<T>(*)(const Math::Vector2<T>&)>(&Math::Matrix3<T>::translation),
"2D translation matrix")
/* Constructors. The translation() / scaling() / rotation() are handled
below as they conflict with member functions. */
.def_static("reflection", &Math::Matrix3<T>::reflection,
"2D reflection matrix")
.def_static("shearing_x", &Math::Matrix3<T>::shearingX,
@ -590,7 +587,8 @@ template<class T> void matrices(
.def("transform_point", &Math::Matrix3<T>::transformPoint,
"Transform a 2D point with the matrix")
/* Properties */
/* Properties. The translation is handled below together with a static
translation(). */
.def_property("right",
static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::right),
[](Math::Matrix3<T>& self, const Math::Vector2<T>& value) { self.right() = value; },
@ -598,40 +596,118 @@ template<class T> void matrices(
.def_property("up",
static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::up),
[](Math::Matrix3<T>& self, const Math::Vector2<T>& value) { self.up() = value; },
"Up-pointing 2D vector")
.def_property("_translation", // TODO
static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::translation),
[](Math::Matrix3<T>& self, const Math::Vector2<T>& value) { self.translation() = value; },
"2D translation part of the matrix")
/* Static/member scaling(). Pybind doesn't support that natively, so
we create a scaling(*args, **kwargs) and dispatch ourselves. */
.def_static("_sscaling", static_cast<Math::Matrix3<T>(*)(const Math::Vector2<T>&)>(&Math::Matrix3<T>::scaling),
"2D scaling matrix")
.def("_iscaling", static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::scaling),
"Non-uniform scaling part of the matrix")
.def("scaling", [matrix3](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix3<T>>(args[0])) {
return matrix3.attr("_iscaling")(*args, **kwargs);
} else {
return matrix3.attr("_sscaling")(*args, **kwargs);
}
})
/* Static/member rotation(). Pybind doesn't support that natively, so
we create a rotation(*args, **kwargs) and dispatch ourselves. */
.def_static("_srotation", [](Radd angle) {
return Math::Matrix3<T>::rotation(Math::Rad<T>(angle));
}, "2D rotation matrix")
.def("_irotation", static_cast<Math::Matrix2x2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::rotation),
"2D rotation part of the matrix")
.def("rotation", [matrix3](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix3<T>>(args[0])) {
return matrix3.attr("_irotation")(*args, **kwargs);
} else {
return matrix3.attr("_srotation")(*args, **kwargs);
}
});
"Up-pointing 2D vector");
/* "Magic" static/member functions and properties. In order to have
reasonable docs, we need to disable pybind's function signatures and
supply ours faked instead. */
{
py::options options;
options.disable_function_signatures();
constexpr const char* ScalingDocstring[] {
R"(scaling(*args, **kwargs)
Overloaded function.
1. scaling(arg0: _magnum.Vector2) -> _magnum.Matrix3
2D scaling matrix
2. scaling(self: _magnum.Matrix3) -> _magnum.Vector2
Non-uniform scaling part of the matrix
)",
R"(scaling(*args, **kwargs)
Overloaded function.
1. scaling(arg0: _magnum.Vector2d) -> _magnum.Matrix3d
2D scaling matrix
2. scaling(self: _magnum.Matrix3d) -> _magnum.Vector2d
Non-uniform scaling part of the matrix
)"};
constexpr const char* RotationDocstring[] {
R"(rotation(*args, **kwargs)
Overloaded function.
1. rotation(arg0: _magnum.Rad) -> _magnum.Matrix3
2D rotation matrix
2. rotation(self: _magnum.Matrix3) -> _magnum.Matrix2x2
2D rotation part of the matrix
)",
R"(rotation(*args, **kwargs)
Overloaded function.
1. rotation(arg0: _magnum.Rad) -> _magnum.Matrix3d
2D rotation matrix
2. rotation(self: _magnum.Matrix3d) -> _magnum.Matrix2x2d
2D rotation part of the matrix
)"};
/* This one is special, as it renames the function */
constexpr const char* TranslationDocstring[] {
R"(_stranslation(*args, **kwargs)
Overloaded function.
1. translation(arg0: _magnum.Vector2) -> _magnum.Matrix3
2D translation matrix
)",
R"(_stranslation(*args, **kwargs)
Overloaded function.
1. translation(arg0: _magnum.Vector2d) -> _magnum.Matrix3d
2D translation matrix
)"};
matrix3
/* Static/member scaling(). Pybind doesn't support that natively,
so we create a scaling(*args, **kwargs) and dispatch ourselves. */
.def_static("_sscaling", static_cast<Math::Matrix3<T>(*)(const Math::Vector2<T>&)>(&Math::Matrix3<T>::scaling))
.def("_iscaling", static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::scaling))
.def("scaling", [matrix3](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix3<T>>(args[0])) {
return matrix3.attr("_iscaling")(*args, **kwargs);
} else {
return matrix3.attr("_sscaling")(*args, **kwargs);
}
}, ScalingDocstring[sizeof(T)/4 - 1])
/* Static/member rotation(). Pybind doesn't support that natively,
so we create a rotation(*args, **kwargs) and dispatch ourselves. */
.def_static("_srotation", [](Radd angle) {
return Math::Matrix3<T>::rotation(Math::Rad<T>(angle));
})
.def("_irotation", static_cast<Math::Matrix2x2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::rotation))
.def("rotation", [matrix3](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix3<T>>(args[0])) {
return matrix3.attr("_irotation")(*args, **kwargs);
} else {
return matrix3.attr("_srotation")(*args, **kwargs);
}
}, RotationDocstring[sizeof(T)/4 - 1])
/* Static translation function, member translation property. This
one is tricky and can't be done without supplying a special
metaclass that replaces static access to `translation` with
`_stranslation`. */
.def_static("_stranslation", static_cast<Math::Matrix3<T>(*)(const Math::Vector2<T>&)>(&Math::Matrix3<T>::translation), std::getenv("MCSS_GENERATING_OUTPUT") ? TranslationDocstring[sizeof(T)/4 - 1] : "");
}
/* The translation property again needs a pybind signature so we can
extract its type */
matrix3.def_property("translation",
static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::translation),
[](Math::Matrix3<T>& self, const Math::Vector2<T>& value) { self.translation() = value; },
"2D translation part of the matrix");
/* 4x4 transformation matrix. Buffer constructors need to be *before* tuple
constructors so numpy buffer protocol gets extracted correctly. */
@ -639,10 +715,8 @@ template<class T> void matrices(
everyRectangularMatrix(matrix4);
everyMatrix(matrix4);
matrix4
/* Constructors. The scaling() / rotation() are handled below
as they conflict with member functions. */
.def_static("translation", static_cast<Math::Matrix4<T>(*)(const Math::Vector3<T>&)>(&Math::Matrix4<T>::translation),
"3D translation matrix")
/* Constructors. The translation() / scaling() / rotation() are handled
below as they conflict with member functions. */
.def_static("rotation_x", [](Radd angle) {
return Math::Matrix4<T>::rotationX(Math::Rad<T>(angle));
}, "3D rotation matrix around the X axis")
@ -715,7 +789,8 @@ template<class T> void matrices(
.def("transform_point", &Math::Matrix4<T>::transformPoint,
"Transform a 3D point with the matrix")
/* Properties */
/* Properties. The translation is handled below together with a static
translation(). */
.def_property("right",
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::right),
[](Math::Matrix4<T>& self, const Math::Vector3<T>& value) { self.right() = value; },
@ -727,40 +802,120 @@ template<class T> void matrices(
.def_property("backward",
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::backward),
[](Math::Matrix4<T>& self, const Math::Vector3<T>& value) { self.backward() = value; },
"Backward-pointing 3D vector")
.def_property("_translation", // TODO
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::translation),
[](Math::Matrix4<T>& self, const Math::Vector3<T>& value) { self.translation() = value; },
"3D translation part of the matrix")
/* Static/member scaling(). Pybind doesn't support that natively, so
we create a scaling(*args, **kwargs) and dispatch ourselves. */
.def_static("_sscaling", static_cast<Math::Matrix4<T>(*)(const Math::Vector3<T>&)>(&Math::Matrix4<T>::scaling),
"3D scaling matrix")
.def("_iscaling", static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::scaling),
"Non-uniform scaling part of the matrix")
.def("scaling", [matrix4](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix4<T>>(args[0])) {
return matrix4.attr("_iscaling")(*args, **kwargs);
} else {
return matrix4.attr("_sscaling")(*args, **kwargs);
}
})
/* Static/member rotation(). Pybind doesn't support that natively, so
we create a rotation(*args, **kwargs) and dispatch ourselves. */
.def_static("_srotation", [](Radd angle, const Math::Vector3<T>& axis) {
return Math::Matrix4<T>::rotation(Math::Rad<T>(angle), axis);
}, "3D rotation matrix around arbitrary axis")
.def("_irotation", static_cast<Math::Matrix3x3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::rotation),
"3D rotation part of the matrix")
.def("rotation", [matrix4](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix4<T>>(args[0])) {
return matrix4.attr("_irotation")(*args, **kwargs);
} else {
return matrix4.attr("_srotation")(*args, **kwargs);
}
});
"Backward-pointing 3D vector");
/* "Magic" static/member functions and properties. In order to have
reasonable docs, we need to disable pybind's function signatures and
supply ours faked instead. */
{
py::options options;
options.disable_function_signatures();
constexpr const char* ScalingDocstring[] {
R"(scaling(*args, **kwargs)
Overloaded function.
1. scaling(arg0: _magnum.Vector3) -> _magnum.Matrix4
3D scaling matrix
2. scaling(self: _magnum.Matrix4) -> _magnum.Vector3
Non-uniform scaling part of the matrix
)",
R"(scaling(*args, **kwargs)
Overloaded function.
1. scaling(arg0: _magnum.Vector3d) -> _magnum.Matrix4d
2D scaling matrix
2. scaling(self: _magnum.Matrix3d) -> _magnum.Vector3d
Non-uniform scaling part of the matrix
)"
};
constexpr const char* RotationDocstring[] {
R"(rotation(*args, **kwargs)
Overloaded function.
1. rotation(arg0: _magnum.Rad, arg1: _magnum.Vector3) -> _magnum.Matrix4
3D rotation matrix
2. rotation(self: _magnum.Matrix3) -> _magnum.Matrix3x3
3D rotation part of the matrix
)",
R"(rotation(*args, **kwargs)
Overloaded function.
1. rotation(arg0: _magnum.Rad, arg1: _magnum.Vector3d) -> _magnum.Matrix4d
3D rotation matrix
2. rotation(self: _magnum.Matrix4d) -> _magnum.Matrix3x3d
3D rotation part of the matrix
)",
};
/* This one is special, as it renames the function */
constexpr const char* TranslationDocstring[] {
R"(_stranslation(*args, **kwargs)
Overloaded function.
1. translation(arg0: _magnum.Vector3) -> _magnum.Matrix4
3D translation matrix
)",
R"(_stranslation(*args, **kwargs)
Overloaded function.
1. translation(arg0: _magnum.Vector3d) -> _magnum.Matrix4d
3D translation matrix
)"};
matrix4
/* Static/member scaling(). Pybind doesn't support that natively,
so we create a scaling(*args, **kwargs) and dispatch ourselves. */
.def_static("_sscaling", static_cast<Math::Matrix4<T>(*)(const Math::Vector3<T>&)>(&Math::Matrix4<T>::scaling))
.def("_iscaling", static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::scaling))
.def("scaling", [matrix4](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix4<T>>(args[0])) {
return matrix4.attr("_iscaling")(*args, **kwargs);
} else {
return matrix4.attr("_sscaling")(*args, **kwargs);
}
}, ScalingDocstring[sizeof(T)/4 - 1])
/* Static/member rotation(). Pybind doesn't support that natively,
so we create a rotation(*args, **kwargs) and dispatch ourselves. */
.def_static("_srotation", [](Radd angle, const Math::Vector3<T>& axis) {
return Math::Matrix4<T>::rotation(Math::Rad<T>(angle), axis);
})
.def("_irotation", static_cast<Math::Matrix3x3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::rotation))
.def("rotation", [matrix4](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix4<T>>(args[0])) {
return matrix4.attr("_irotation")(*args, **kwargs);
} else {
return matrix4.attr("_srotation")(*args, **kwargs);
}
}, RotationDocstring[sizeof(T)/4 - 1])
/* Static translation function, member translation property. This
one is tricky and can't be done without supplying a special
metaclass that replaces static access to `translation` with
`_stranslation`. */
.def_static("_stranslation", static_cast<Math::Matrix4<T>(*)(const Math::Vector3<T>&)>(&Math::Matrix4<T>::translation), std::getenv("MCSS_GENERATING_OUTPUT") ? TranslationDocstring[sizeof(T)/4 - 1] : "");
}
/* The translation property again needs a pybind signature so we can
extract its type */
matrix4.def_property("translation",
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::translation),
[](Math::Matrix4<T>& self, const Math::Vector3<T>& value) { self.translation() = value; },
"3D translation part of the matrix");
}
}

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

@ -27,7 +27,7 @@
namespace magnum {
void mathMatrixDouble(py::module& root) {
void mathMatrixDouble(py::module& root, PyTypeObject* const metaclass) {
py::class_<Matrix2x2d> matrix2x2d{root, "Matrix2x2d", "2x2 double matrix", py::buffer_protocol{}};
py::class_<Matrix2x3d> matrix2x3d{root, "Matrix2x3d", "2x3 double matrix", py::buffer_protocol{}};
py::class_<Matrix2x4d> matrix2x4d{root, "Matrix2x4d", "2x4 double matrix", py::buffer_protocol{}};
@ -43,9 +43,11 @@ void mathMatrixDouble(py::module& root) {
/* 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"};
anywhere, thus failing with `pybind11_getbuffer(): Internal error`. The
metaclasses are needed for supporting the magic translation attribute,
see transformationMatrixMetaclass() in math.cpp for more information. */
py::class_<Matrix3d, Matrix3x3d> matrix3d{root, "Matrix3d", "2D double transformation matrix", py::metaclass(reinterpret_cast<PyObject*>(metaclass))};
py::class_<Matrix4d, Matrix4x4d> matrix4d{root, "Matrix4d", "3D double transformation matrix", py::metaclass(reinterpret_cast<PyObject*>(metaclass))};
/* Register type conversions as soon as possible as those should have a
priority over buffer and list constructors. These need all the types to

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

@ -27,7 +27,7 @@
namespace magnum {
void mathMatrixFloat(py::module& root) {
void mathMatrixFloat(py::module& root, PyTypeObject* const metaclass) {
py::class_<Matrix2x2> matrix2x2{root, "Matrix2x2", "2x2 float matrix", py::buffer_protocol{}};
py::class_<Matrix2x3> matrix2x3{root, "Matrix2x3", "2x3 float matrix", py::buffer_protocol{}};
py::class_<Matrix2x4> matrix2x4{root, "Matrix2x4", "2x4 float matrix", py::buffer_protocol{}};
@ -43,13 +43,15 @@ void mathMatrixFloat(py::module& root) {
/* 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"};
anywhere, thus failing with `pybind11_getbuffer(): Internal error`. The
metaclasses are needed for supporting the magic translation attribute,
see transformationMatrixMetaclass() in math.cpp for more information. */
py::class_<Matrix3, Matrix3x3> matrix3{root, "Matrix3", "2D float transformation matrix", py::metaclass(reinterpret_cast<PyObject*>(metaclass))};
py::class_<Matrix4, Matrix4x4> matrix4{root, "Matrix4", "3D float transformation matrix", py::metaclass(reinterpret_cast<PyObject*>(metaclass))};
/* Register the double types as well, only after that register type
conversions because they need all the types */
mathMatrixDouble(root);
mathMatrixDouble(root, metaclass);
/* Register type conversions as soon as possible as those should have a
priority over buffer and list constructors. These need all the types to

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

@ -680,7 +680,7 @@ class Matrix3_(unittest.TestCase):
def test_static_methods(self):
a = Matrix3.translation((0.0, -1.0))
self.assertEqual(a[2].xy, Vector2(0.0, -1.0))
self.assertEqual(a._translation, Vector2(0.0, -1.0)) # TODO
self.assertEqual(a.translation, Vector2(0.0, -1.0))
b = Matrix3.rotation(Deg(45.0))
self.assertEqual(b.rotation(), Matrix2x2(
@ -694,11 +694,11 @@ class Matrix3_(unittest.TestCase):
a = Matrix3.translation(Vector2.y_axis(-5.0))@Matrix3.rotation(Deg(45.0))
self.assertEqual(a.right, Vector2(0.707107, 0.707107))
self.assertEqual(a.up, Vector2(-0.707107, 0.707107))
self.assertEqual(a._translation, Vector2.y_axis(-5.0)) # TODO
self.assertEqual(a.translation, Vector2.y_axis(-5.0))
a.right = Vector2.x_axis(2.0)
a.up = -Vector2.y_axis()
a._translation = Vector2(0.0) # TODO
a.translation = Vector2(0.0)
self.assertEqual(a, Matrix3.from_diagonal((2.0, -1.0, 1.0)))
def test_methods(self):
@ -796,7 +796,7 @@ class Matrix4_(unittest.TestCase):
def test_static_methods(self):
a = Matrix4.translation((0.0, -1.0, 2.0))
self.assertEqual(a[3].xyz, Vector3(0.0, -1.0, 2.0))
self.assertEqual(a._translation, Vector3(0.0, -1.0, 2.0)) # TODO
self.assertEqual(a.translation, Vector3(0.0, -1.0, 2.0))
b = Matrix4.rotation(Deg(45.0), Vector3.x_axis())
self.assertEqual(b.rotation(), Matrix3x3(
@ -812,12 +812,12 @@ class Matrix4_(unittest.TestCase):
self.assertEqual(a.right, Vector3(0.707107, 0.707107, 0.0))
self.assertEqual(a.up, Vector3(-0.707107, 0.707107, 0.0))
self.assertEqual(a.backward, Vector3(0.0, 0.0, 1.0))
self.assertEqual(a._translation, Vector3.y_axis(-5.0)) # TODO
self.assertEqual(a.translation, Vector3.y_axis(-5.0))
a.right = Vector3.x_axis(3.0)
a.up = -Vector3.y_axis()
a.backward = Vector3.z_axis(2.0)
a._translation = Vector3(0.0) # TODO
a.translation = Vector3(0.0)
self.assertEqual(a, Matrix4.from_diagonal((3.0, -1.0, 2.0, 1.0)))
def test_methods(self):

4
src/python/magnum/test/test_math_numpy.py

@ -48,7 +48,7 @@ class Vector(unittest.TestCase):
a.xyz = np.array([1.0, 2.0, 3.0])
b = Matrix4.translation(np.array([1.0, 2.0, 3.0]))
self.assertEqual(b._translation, Vector3(1.0, 2.0, 3.0))
self.assertEqual(b.translation, Vector3(1.0, 2.0, 3.0))
def test_from_numpy_implicit_typed(self):
# But this doesn't, works only if buffer protocol is defined
@ -56,7 +56,7 @@ class Vector(unittest.TestCase):
a.xyz = np.array([1.0, 2.0, 3.0], dtype='float32')
a = Matrix4.translation(np.array([1.0, 2.0, 3.0], dtype='float32'))
self.assertEqual(a._translation, Vector3(1.0, 2.0, 3.0))
self.assertEqual(a.translation, Vector3(1.0, 2.0, 3.0))
def test_from_numpy_invalid_dimensions(self):
a = np.array([[1, 2], [3, 4]])

Loading…
Cancel
Save