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

6
src/python/magnum/bootstrap.h

@ -25,6 +25,8 @@
DEALINGS IN THE SOFTWARE. DEALINGS IN THE SOFTWARE.
*/ */
#include <Python.h>
namespace pybind11 { class module; } namespace pybind11 { class module; }
namespace Magnum {} namespace Magnum {}
@ -36,8 +38,8 @@ namespace py = pybind11;
void math(py::module& root, py::module& m); void math(py::module& root, py::module& m);
void mathVectorFloat(py::module& root, py::module& m); void mathVectorFloat(py::module& root, py::module& m);
void mathVectorIntegral(py::module& root, py::module& m); void mathVectorIntegral(py::module& root, py::module& m);
void mathMatrixFloat(py::module& root); void mathMatrixFloat(py::module& root, PyTypeObject* metaclass);
void mathMatrixDouble(py::module& root); void mathMatrixDouble(py::module& root, PyTypeObject* metaclass);
void mathRange(py::module& root, py::module& m); void mathRange(py::module& root, py::module& m);
void gl(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"); .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) { 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("acos", [](Double angle) { return Math::acos(angle); }, "Arc cosine")
.def("atan", [](Double angle) { return Math::atan(angle); }, "Arc tangent"); .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::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 */ /* Quaternion */
py::class_<Quaternion> quaternion_(root, "Quaternion", "Float 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; return self*other;
}, "Multiply a matrix"); }, "Multiply a matrix");
/* 3x3 transformation matrix. Buffer constructors need to be *before* tuple /* 3x3 transformation matrix. Buffer constructors need to be *before* tuple
constructors so numpy buffer protocol gets extracted correctly. */ constructors so numpy buffer protocol gets extracted correctly. */
py::implicitly_convertible<Math::Matrix3x3<T>, Math::Matrix3<T>>(); py::implicitly_convertible<Math::Matrix3x3<T>, Math::Matrix3<T>>();
everyRectangularMatrix(matrix3); everyRectangularMatrix(matrix3);
everyMatrix(matrix3); everyMatrix(matrix3);
matrix3 matrix3
/* Constructors. The scaling() / rotation() are handled below /* Constructors. The translation() / scaling() / rotation() are handled
as they conflict with member functions. */ 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")
.def_static("reflection", &Math::Matrix3<T>::reflection, .def_static("reflection", &Math::Matrix3<T>::reflection,
"2D reflection matrix") "2D reflection matrix")
.def_static("shearing_x", &Math::Matrix3<T>::shearingX, .def_static("shearing_x", &Math::Matrix3<T>::shearingX,
@ -590,7 +587,8 @@ template<class T> void matrices(
.def("transform_point", &Math::Matrix3<T>::transformPoint, .def("transform_point", &Math::Matrix3<T>::transformPoint,
"Transform a 2D point with the matrix") "Transform a 2D point with the matrix")
/* Properties */ /* Properties. The translation is handled below together with a static
translation(). */
.def_property("right", .def_property("right",
static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::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; }, [](Math::Matrix3<T>& self, const Math::Vector2<T>& value) { self.right() = value; },
@ -598,40 +596,118 @@ template<class T> void matrices(
.def_property("up", .def_property("up",
static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::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; }, [](Math::Matrix3<T>& self, const Math::Vector2<T>& value) { self.up() = value; },
"Up-pointing 2D vector") "Up-pointing 2D vector");
.def_property("_translation", // TODO
static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::translation), /* "Magic" static/member functions and properties. In order to have
[](Math::Matrix3<T>& self, const Math::Vector2<T>& value) { self.translation() = value; }, reasonable docs, we need to disable pybind's function signatures and
"2D translation part of the matrix") supply ours faked instead. */
{
/* Static/member scaling(). Pybind doesn't support that natively, so py::options options;
we create a scaling(*args, **kwargs) and dispatch ourselves. */ options.disable_function_signatures();
.def_static("_sscaling", static_cast<Math::Matrix3<T>(*)(const Math::Vector2<T>&)>(&Math::Matrix3<T>::scaling),
"2D scaling matrix") constexpr const char* ScalingDocstring[] {
.def("_iscaling", static_cast<Math::Vector2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::scaling), R"(scaling(*args, **kwargs)
"Non-uniform scaling part of the matrix") Overloaded function.
.def("scaling", [matrix3](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix3<T>>(args[0])) { 1. scaling(arg0: _magnum.Vector2) -> _magnum.Matrix3
return matrix3.attr("_iscaling")(*args, **kwargs);
} else { 2D scaling matrix
return matrix3.attr("_sscaling")(*args, **kwargs);
} 2. scaling(self: _magnum.Matrix3) -> _magnum.Vector2
})
Non-uniform scaling part of the matrix
/* Static/member rotation(). Pybind doesn't support that natively, so )",
we create a rotation(*args, **kwargs) and dispatch ourselves. */ R"(scaling(*args, **kwargs)
.def_static("_srotation", [](Radd angle) { Overloaded function.
return Math::Matrix3<T>::rotation(Math::Rad<T>(angle));
}, "2D rotation matrix") 1. scaling(arg0: _magnum.Vector2d) -> _magnum.Matrix3d
.def("_irotation", static_cast<Math::Matrix2x2<T>(Math::Matrix3<T>::*)() const>(&Math::Matrix3<T>::rotation),
"2D rotation part of the matrix") 2D scaling matrix
.def("rotation", [matrix3](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix3<T>>(args[0])) { 2. scaling(self: _magnum.Matrix3d) -> _magnum.Vector2d
return matrix3.attr("_irotation")(*args, **kwargs);
} else { Non-uniform scaling part of the matrix
return matrix3.attr("_srotation")(*args, **kwargs); )"};
} 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 /* 4x4 transformation matrix. Buffer constructors need to be *before* tuple
constructors so numpy buffer protocol gets extracted correctly. */ constructors so numpy buffer protocol gets extracted correctly. */
@ -639,10 +715,8 @@ template<class T> void matrices(
everyRectangularMatrix(matrix4); everyRectangularMatrix(matrix4);
everyMatrix(matrix4); everyMatrix(matrix4);
matrix4 matrix4
/* Constructors. The scaling() / rotation() are handled below /* Constructors. The translation() / scaling() / rotation() are handled
as they conflict with member functions. */ 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")
.def_static("rotation_x", [](Radd angle) { .def_static("rotation_x", [](Radd angle) {
return Math::Matrix4<T>::rotationX(Math::Rad<T>(angle)); return Math::Matrix4<T>::rotationX(Math::Rad<T>(angle));
}, "3D rotation matrix around the X axis") }, "3D rotation matrix around the X axis")
@ -715,7 +789,8 @@ template<class T> void matrices(
.def("transform_point", &Math::Matrix4<T>::transformPoint, .def("transform_point", &Math::Matrix4<T>::transformPoint,
"Transform a 3D point with the matrix") "Transform a 3D point with the matrix")
/* Properties */ /* Properties. The translation is handled below together with a static
translation(). */
.def_property("right", .def_property("right",
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::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; }, [](Math::Matrix4<T>& self, const Math::Vector3<T>& value) { self.right() = value; },
@ -727,40 +802,120 @@ template<class T> void matrices(
.def_property("backward", .def_property("backward",
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::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; }, [](Math::Matrix4<T>& self, const Math::Vector3<T>& value) { self.backward() = value; },
"Backward-pointing 3D vector") "Backward-pointing 3D vector");
.def_property("_translation", // TODO
static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::translation), /* "Magic" static/member functions and properties. In order to have
[](Math::Matrix4<T>& self, const Math::Vector3<T>& value) { self.translation() = value; }, reasonable docs, we need to disable pybind's function signatures and
"3D translation part of the matrix") supply ours faked instead. */
{
/* Static/member scaling(). Pybind doesn't support that natively, so py::options options;
we create a scaling(*args, **kwargs) and dispatch ourselves. */ options.disable_function_signatures();
.def_static("_sscaling", static_cast<Math::Matrix4<T>(*)(const Math::Vector3<T>&)>(&Math::Matrix4<T>::scaling),
"3D scaling matrix") constexpr const char* ScalingDocstring[] {
.def("_iscaling", static_cast<Math::Vector3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::scaling), R"(scaling(*args, **kwargs)
"Non-uniform scaling part of the matrix") Overloaded function.
.def("scaling", [matrix4](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix4<T>>(args[0])) { 1. scaling(arg0: _magnum.Vector3) -> _magnum.Matrix4
return matrix4.attr("_iscaling")(*args, **kwargs);
} else { 3D scaling matrix
return matrix4.attr("_sscaling")(*args, **kwargs);
} 2. scaling(self: _magnum.Matrix4) -> _magnum.Vector3
})
Non-uniform scaling part of the matrix
/* Static/member rotation(). Pybind doesn't support that natively, so )",
we create a rotation(*args, **kwargs) and dispatch ourselves. */ R"(scaling(*args, **kwargs)
.def_static("_srotation", [](Radd angle, const Math::Vector3<T>& axis) { Overloaded function.
return Math::Matrix4<T>::rotation(Math::Rad<T>(angle), axis);
}, "3D rotation matrix around arbitrary axis") 1. scaling(arg0: _magnum.Vector3d) -> _magnum.Matrix4d
.def("_irotation", static_cast<Math::Matrix3x3<T>(Math::Matrix4<T>::*)() const>(&Math::Matrix4<T>::rotation),
"3D rotation part of the matrix") 2D scaling matrix
.def("rotation", [matrix4](py::args args, py::kwargs kwargs) {
if(py::len(args) && py::isinstance<Math::Matrix4<T>>(args[0])) { 2. scaling(self: _magnum.Matrix3d) -> _magnum.Vector3d
return matrix4.attr("_irotation")(*args, **kwargs);
} else { Non-uniform scaling part of the matrix
return matrix4.attr("_srotation")(*args, **kwargs); )"
} };
}); 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 { 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_<Matrix2x2d> matrix2x2d{root, "Matrix2x2d", "2x2 double matrix", py::buffer_protocol{}};
py::class_<Matrix2x3d> matrix2x3d{root, "Matrix2x3d", "2x3 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{}}; 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 /* The subclasses don't have buffer protocol enabled, as that's already
done by the base classes. Moreover, just adding py::buffer_protocol{} 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 would cause it to not find the buffer functions as we don't add them
anywhere, thus failing with `pybind11_getbuffer(): Internal error`. */ anywhere, thus failing with `pybind11_getbuffer(): Internal error`. The
py::class_<Matrix3d, Matrix3x3d> matrix3d{root, "Matrix3d", "2D double transformation matrix"}; metaclasses are needed for supporting the magic translation attribute,
py::class_<Matrix4d, Matrix4x4d> matrix4d{root, "Matrix4d", "3D double transformation matrix"}; 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 /* Register type conversions as soon as possible as those should have a
priority over buffer and list constructors. These need all the types to 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 { 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_<Matrix2x2> matrix2x2{root, "Matrix2x2", "2x2 float matrix", py::buffer_protocol{}};
py::class_<Matrix2x3> matrix2x3{root, "Matrix2x3", "2x3 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{}}; 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 /* The subclasses don't have buffer protocol enabled, as that's already
done by the base classes. Moreover, just adding py::buffer_protocol{} 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 would cause it to not find the buffer functions as we don't add them
anywhere, thus failing with `pybind11_getbuffer(): Internal error`. */ anywhere, thus failing with `pybind11_getbuffer(): Internal error`. The
py::class_<Matrix3, Matrix3x3> matrix3{root, "Matrix3", "2D float transformation matrix"}; metaclasses are needed for supporting the magic translation attribute,
py::class_<Matrix4, Matrix4x4> matrix4{root, "Matrix4", "3D float transformation matrix"}; 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 /* Register the double types as well, only after that register type
conversions because they need all the types */ conversions because they need all the types */
mathMatrixDouble(root); mathMatrixDouble(root, metaclass);
/* Register type conversions as soon as possible as those should have a /* Register type conversions as soon as possible as those should have a
priority over buffer and list constructors. These need all the types to 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): def test_static_methods(self):
a = Matrix3.translation((0.0, -1.0)) a = Matrix3.translation((0.0, -1.0))
self.assertEqual(a[2].xy, Vector2(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)) b = Matrix3.rotation(Deg(45.0))
self.assertEqual(b.rotation(), Matrix2x2( 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)) 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.right, Vector2(0.707107, 0.707107))
self.assertEqual(a.up, 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.right = Vector2.x_axis(2.0)
a.up = -Vector2.y_axis() 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))) self.assertEqual(a, Matrix3.from_diagonal((2.0, -1.0, 1.0)))
def test_methods(self): def test_methods(self):
@ -796,7 +796,7 @@ class Matrix4_(unittest.TestCase):
def test_static_methods(self): def test_static_methods(self):
a = Matrix4.translation((0.0, -1.0, 2.0)) a = Matrix4.translation((0.0, -1.0, 2.0))
self.assertEqual(a[3].xyz, Vector3(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()) b = Matrix4.rotation(Deg(45.0), Vector3.x_axis())
self.assertEqual(b.rotation(), Matrix3x3( 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.right, Vector3(0.707107, 0.707107, 0.0))
self.assertEqual(a.up, 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.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.right = Vector3.x_axis(3.0)
a.up = -Vector3.y_axis() a.up = -Vector3.y_axis()
a.backward = Vector3.z_axis(2.0) 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))) self.assertEqual(a, Matrix4.from_diagonal((3.0, -1.0, 2.0, 1.0)))
def test_methods(self): 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]) a.xyz = np.array([1.0, 2.0, 3.0])
b = Matrix4.translation(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): def test_from_numpy_implicit_typed(self):
# But this doesn't, works only if buffer protocol is defined # 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.xyz = np.array([1.0, 2.0, 3.0], dtype='float32')
a = Matrix4.translation(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): def test_from_numpy_invalid_dimensions(self):
a = np.array([[1, 2], [3, 4]]) a = np.array([[1, 2], [3, 4]])

Loading…
Cancel
Save