From f561337caa9094c4a99411fac402342343f4ecb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 26 Oct 2023 20:14:13 +0200 Subject: [PATCH] python: implement pickling for all math types. --- doc/python/magnum.math.rst | 322 ++++++++++++++++++++++++++++ src/python/magnum/math.cpp | 46 ++++ src/python/magnum/math.matrix.h | 18 ++ src/python/magnum/math.range.cpp | 18 ++ src/python/magnum/math.vector.h | 18 ++ src/python/magnum/test/test_math.py | 75 +++++++ 6 files changed, 497 insertions(+) diff --git a/doc/python/magnum.math.rst b/doc/python/magnum.math.rst index dcc5ec2..7b72f7e 100644 --- a/doc/python/magnum.math.rst +++ b/doc/python/magnum.math.rst @@ -229,3 +229,325 @@ :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 :ref:`Matrix3` class. + +.. For pickling, because py::pickle() doesn't seem to have a way to set the + __setstate__ / __getstate__ docs directly FFS + +.. py:function:: magnum.Deg.__getstate__ + :summary: Dumps the in-memory representation as a float +.. py:function:: magnum.Rad.__getstate__ + :summary: Dumps the in-memory representation as a float + +.. py:function:: magnum.BitVector2.__getstate__ + :summary: Dumps the in-memory representation of vector bits +.. py:function:: magnum.BitVector3.__getstate__ + :summary: Dumps the in-memory representation of vector bits +.. py:function:: magnum.BitVector4.__getstate__ + :summary: Dumps the in-memory representation of vector bits + +.. py:function:: magnum.Vector2.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector3.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector4.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector2d.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector3d.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector4d.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector2i.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector3i.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector4i.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector2ui.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector3ui.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Vector4ui.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Color3.__getstate__ + :summary: Dumps the in-memory representation of vector components +.. py:function:: magnum.Color4.__getstate__ + :summary: Dumps the in-memory representation of vector components + +.. py:function:: magnum.Matrix2x2.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix2x3.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix2x4.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix3x2.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix3x3.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix3x4.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix4x2.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix4x3.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix4x4.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix2x2d.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix2x3d.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix2x4d.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix3x2d.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix3x3d.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix3x4d.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix4x2d.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix4x3d.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix4x4d.__getstate__ + :summary: Dumps the in-memory representation of matrix components + +.. py:function:: magnum.Matrix3.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix4.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix3d.__getstate__ + :summary: Dumps the in-memory representation of matrix components +.. py:function:: magnum.Matrix4d.__getstate__ + :summary: Dumps the in-memory representation of matrix components + +.. py:function:: magnum.Quaternion.__getstate__ + :summary: Dumps the in-memory representation of quaternion components +.. py:function:: magnum.Quaterniond.__getstate__ + :summary: Dumps the in-memory representation of quaternion components + +.. py:function:: magnum.Range1D.__getstate__ + :summary: Dumps the in-memory representation of range min/max components +.. py:function:: magnum.Range2D.__getstate__ + :summary: Dumps the in-memory representation of range min/max components +.. py:function:: magnum.Range3D.__getstate__ + :summary: Dumps the in-memory representation of range min/max components +.. py:function:: magnum.Range1Dd.__getstate__ + :summary: Dumps the in-memory representation of range min/max components +.. py:function:: magnum.Range2Dd.__getstate__ + :summary: Dumps the in-memory representation of range min/max components +.. py:function:: magnum.Range3Dd.__getstate__ + :summary: Dumps the in-memory representation of range min/max components +.. py:function:: magnum.Range1Di.__getstate__ + :summary: Dumps the in-memory representation of range min/max components +.. py:function:: magnum.Range2Di.__getstate__ + :summary: Dumps the in-memory representation of range min/max components +.. py:function:: magnum.Range3Di.__getstate__ + :summary: Dumps the in-memory representation of range min/max components + +.. py:function:: magnum.Deg.__setstate__ + :summary: Uses the float as the in-memory representation +.. py:function:: magnum.Rad.__setstate__ + :summary: Uses the float as the in-memory representation + +.. py:function:: magnum.BitVector2.__setstate__ + :summary: Treats the data as the in-memory representation of vector bits + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.BitVector3.__setstate__ + :summary: Treats the data as the in-memory representation of vector bits + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.BitVector4.__setstate__ + :summary: Treats the data as the in-memory representation of vector bits + :raise ValueError: If the data size doesn't match type size + +.. py:function:: magnum.Vector2.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector3.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector4.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector2d.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector3d.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector4d.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector2i.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector3i.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector4i.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector2ui.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector3ui.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Vector4ui.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Color3.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Color4.__setstate__ + :summary: Treats the data as the in-memory representation of vector + components + :raise ValueError: If the data size doesn't match type size + +.. py:function:: magnum.Matrix2x2.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix2x3.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix2x4.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix3x2.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix3x3.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix3x4.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix4x2.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix4x3.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix4x4.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix2x2d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix2x3d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix2x4d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix3x2d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix3x3d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix3x4d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix4x2d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix4x3d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix4x4d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size + +.. py:function:: magnum.Matrix3.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix4.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix3d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Matrix4d.__setstate__ + :summary: Treats the data as the in-memory representation of matrix + components + :raise ValueError: If the data size doesn't match type size + +.. py:function:: magnum.Quaternion.__setstate__ + :summary: Treats the data as the in-memory representation of quaternion + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Quaterniond.__setstate__ + :summary: Treats the data as the in-memory representation of quaternion + components + :raise ValueError: If the data size doesn't match type size + +.. py:function:: magnum.Range1D.__setstate__ + :summary: Treats the data as the in-memory representation of range min/max + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Range2D.__setstate__ + :summary: Treats the data as the in-memory representation of range min/max + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Range3D.__setstate__ + :summary: Treats the data as the in-memory representation of range min/max + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Range1Dd.__setstate__ + :summary: Treats the data as the in-memory representation of range min/max + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Range2Dd.__setstate__ + :summary: Treats the data as the in-memory representation of range min/max + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Range3Dd.__setstate__ + :summary: Treats the data as the in-memory representation of range min/max + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Range1Di.__setstate__ + :summary: Treats the data as the in-memory representation of range min/max + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Range2Di.__setstate__ + :summary: Treats the data as the in-memory representation of range min/max + components + :raise ValueError: If the data size doesn't match type size +.. py:function:: magnum.Range3Di.__setstate__ + :summary: Treats the data as the in-memory representation of range min/max + components + :raise ValueError: If the data size doesn't match type size diff --git a/src/python/magnum/math.cpp b/src/python/magnum/math.cpp index 42fe7b1..6c1b58f 100644 --- a/src/python/magnum/math.cpp +++ b/src/python/magnum/math.cpp @@ -116,6 +116,16 @@ template void angle(py::module_& m, py::class_& c) { .def(py::self <= py::self, "Less than or equal comparison") .def(py::self >= py::self, "Greater than or equal comparison") + /* Pickling */ + .def(py::pickle( + [](const T& self) { + return typename T::Type(self); + }, + [](typename T::Type data) { + return T(data); + } + )) + /* Arithmetic ops. Need to use lambdas because the C++ functions return the Unit base class :( */ .def("__neg__", [](const T& self) -> T { @@ -195,6 +205,24 @@ template void bitVector(py::module_& m, py::class_& c) { .def(py::self == py::self, "Equality comparison") .def(py::self != py::self, "Non-equality comparison") + /* Pickling */ + .def(py::pickle( + [](const T& self) { + return py::bytes(reinterpret_cast(self.data()), sizeof(T)); + }, + [](const py::bytes& data) { + const std::size_t size = PyBytes_GET_SIZE(data.ptr()); + if(size != sizeof(T)) { + PyErr_Format(PyExc_ValueError, "expected %zu bytes but got %zi", sizeof(T), size); + throw py::error_already_set{}; + } + T out; + /** @todo gah is there really no other way to access contents? */ + std::memcpy(out.data(), PyBytes_AS_STRING(data.ptr()), sizeof(T)); + return out; + } + )) + /* Member functions */ .def("all", &T::all, "Whether all bits are set") .def("none", &T::none, "Whether no bits are set") @@ -314,6 +342,24 @@ template void quaternion(py::module_& m, py::class_& c) { .def(py::self == py::self, "Equality comparison") .def(py::self != py::self, "Non-equality comparison") + /* Pickling */ + .def(py::pickle( + [](const T& self) { + return py::bytes(reinterpret_cast(self.data()), sizeof(T)); + }, + [](const py::bytes& data) { + const std::size_t size = PyBytes_GET_SIZE(data.ptr()); + if(size != sizeof(T)) { + PyErr_Format(PyExc_ValueError, "expected %zu bytes but got %zi", sizeof(T), size); + throw py::error_already_set{}; + } + T out; + /** @todo gah is there really no other way to access contents? */ + std::memcpy(out.data(), PyBytes_AS_STRING(data.ptr()), sizeof(T)); + return out; + } + )) + /* Operators */ .def(-py::self, "Negated quaternion") .def(py::self += py::self, "Add and assign a quaternion") diff --git a/src/python/magnum/math.matrix.h b/src/python/magnum/math.matrix.h index f1939d9..eee29cb 100644 --- a/src/python/magnum/math.matrix.h +++ b/src/python/magnum/math.matrix.h @@ -67,6 +67,24 @@ template void everyRectangularMatrix(py::class_(), "Construct a matrix with one value for all components") + /* Pickling */ + .def(py::pickle( + [](const T& self) { + return py::bytes(reinterpret_cast(self.data()), sizeof(T)); + }, + [](const py::bytes& data) { + const std::size_t size = PyBytes_GET_SIZE(data.ptr()); + if(size != sizeof(T)) { + PyErr_Format(PyExc_ValueError, "expected %zu bytes but got %zi", sizeof(T), size); + throw py::error_already_set{}; + } + T out; + /** @todo gah is there really no other way to access contents? */ + std::memcpy(out.data(), PyBytes_AS_STRING(data.ptr()), sizeof(T)); + return out; + } + )) + /* Operators */ .def(-py::self, "Negated matrix") .def(py::self += py::self, "Add and assign a matrix") diff --git a/src/python/magnum/math.range.cpp b/src/python/magnum/math.range.cpp index c5be3d4..a347ae3 100644 --- a/src/python/magnum/math.range.cpp +++ b/src/python/magnum/math.range.cpp @@ -73,6 +73,24 @@ template void range(py::module_& m, py::class_& c) { [](T& self, const typename T::VectorType& value) { self.max() = value; }, "Maximal coordinates (exclusive)") + /* Pickling */ + .def(py::pickle( + [](const T& self) { + return py::bytes(reinterpret_cast(self.data()), sizeof(T)); + }, + [](const py::bytes& data) { + const std::size_t size = PyBytes_GET_SIZE(data.ptr()); + if(size != sizeof(T)) { + PyErr_Format(PyExc_ValueError, "expected %zu bytes but got %zi", sizeof(T), size); + throw py::error_already_set{}; + } + T out; + /** @todo gah is there really no other way to access contents? */ + std::memcpy(out.data(), PyBytes_AS_STRING(data.ptr()), sizeof(T)); + return out; + } + )) + /* Methods */ .def("size", &T::size, "Range size") .def("center", &T::center, "Range center") diff --git a/src/python/magnum/math.vector.h b/src/python/magnum/math.vector.h index 99865ab..3ee9b14 100644 --- a/src/python/magnum/math.vector.h +++ b/src/python/magnum/math.vector.h @@ -92,6 +92,24 @@ template void everyVector(py::class_& c) { }, "Construct a zero vector") .def(py::init(), "Default constructor") + /* Pickling */ + .def(py::pickle( + [](const T& self) { + return py::bytes(reinterpret_cast(self.data()), sizeof(T)); + }, + [](const py::bytes& data) { + const std::size_t size = PyBytes_GET_SIZE(data.ptr()); + if(size != sizeof(T)) { + PyErr_Format(PyExc_ValueError, "expected %zu bytes but got %zi", sizeof(T), size); + throw py::error_already_set{}; + } + T out; + /** @todo gah is there really no other way to access contents? */ + std::memcpy(out.data(), PyBytes_AS_STRING(data.ptr()), sizeof(T)); + return out; + } + )) + /* Operators */ .def(py::self += py::self, "Add and assign a vector") .def(py::self + py::self, "Add a vector") diff --git a/src/python/magnum/test/test_math.py b/src/python/magnum/test/test_math.py index 52bf572..a713f6c 100644 --- a/src/python/magnum/test/test_math.py +++ b/src/python/magnum/test/test_math.py @@ -24,6 +24,7 @@ # import array +import pickle import unittest from magnum import * @@ -38,6 +39,12 @@ class Angle(unittest.TestCase): self.assertEqual(b, Rad(0.0)) self.assertEqual(c, Deg(90.0)) + def test_pickle(self): + data = pickle.dumps(Deg(45.0)) + self.assertEqual(pickle.loads(data), Deg(45.0)) + + # There's no way for this pickle to fail + def test_conversion(self): self.assertEqual(Rad(Deg(90.0)), Rad(math.pi_half)) @@ -80,6 +87,12 @@ class BitVector(unittest.TestCase): self.assertEqual(b, BitVector2(0b00)) self.assertEqual(c, BitVector2(0b11)) + def test_pickle(self): + data = pickle.dumps(BitVector4(0b1010)) + self.assertEqual(pickle.loads(data), BitVector4(0b1010)) + + # TODO how to test pickle failure?! direct __setstate__ doesn't work :/ + def test_length(self): self.assertEqual(BitVector3.__len__(), 3) #self.assertEqual(len(BitVector3), 3) TODO: Y not? @@ -246,6 +259,12 @@ class Vector(unittest.TestCase): self.assertEqual(d, Vector3(1.0, 0.3, 1.1)) self.assertEqual(e, Vector4d(1.0, 0.3, 1.1, 0.5)) + def test_pickle(self): + data = pickle.dumps(Vector4d(1.0, 0.3, 1.1, 0.5)) + self.assertEqual(pickle.loads(data), Vector4d(1.0, 0.3, 1.1, 0.5)) + + # TODO how to test pickle failure?! direct __setstate__ doesn't work :/ + def test_convert(self): a = Vector2i(Vector2(4.3, 3.1)) self.assertEqual(a, Vector2i(4, 3)) @@ -500,6 +519,12 @@ class Color3_(unittest.TestCase): self.assertEqual(blue, Color3(0.0, 0.0, 0.5)) self.assertEqual(yellow, Color3(1.0, 1.0, 0.5)) + def test_pickle(self): + data = pickle.dumps(Color3(0.107177, 0.160481, 0.427)) + self.assertEqual(pickle.loads(data), Color3(0.107177, 0.160481, 0.427)) + + # TODO how to test pickle failure?! direct __setstate__ doesn't work :/ + def test_srgb(self): # Cross-checked with C++ tests a = Color3.from_srgb_int(0xf32a80) @@ -576,6 +601,12 @@ class Color4_(unittest.TestCase): self.assertEqual(blue, Color4(0.0, 0.0, 0.5, 0.75)) self.assertEqual(yellow, Color4(1.0, 1.0, 0.5, 0.75)) + def test_pickle(self): + data = pickle.dumps(Color4(0.107177, 0.160481, 0.427, 0.5)) + self.assertEqual(pickle.loads(data), Color4(0.107177, 0.160481, 0.427, 0.5)) + + # TODO how to test pickle failure?! direct __setstate__ doesn't work :/ + def test_srgb(self): # Cross-checked with C++ tests a = Color4.from_srgb_int(0xf32a80) @@ -824,6 +855,16 @@ class Matrix(unittest.TestCase): (0.0, 2.0, 0.0, 0.0), (0.0, 0.0, 3.0, 0.0))) + def test_pickle(self): + data = pickle.dumps(Matrix3x2((1.0, 2.0), + (3.0, 4.0), + (5.0, 6.0))) + self.assertEqual(pickle.loads(data), Matrix3x2((1.0, 2.0), + (3.0, 4.0), + (5.0, 6.0))) + + # TODO how to test pickle failure?! direct __setstate__ doesn't work :/ + def test_convert(self): a = Matrix2x3d(Matrix2x3((1.0, 2.0, 3.0), (4.0, 5.0, 6.0))) @@ -971,6 +1012,16 @@ class Matrix3_(unittest.TestCase): c = Matrix3.scaling((1.0, 2.0)) self.assertEqual(c.scaling(), Vector2(1.0, 2.0)) + def test_pickle(self): + data = pickle.dumps(Matrix3((1.0, 2.0, 3.0), + (4.0, 5.0, 6.0), + (7.0, 8.0, 9.0))) + self.assertEqual(pickle.loads(data), Matrix3((1.0, 2.0, 3.0), + (4.0, 5.0, 6.0), + (7.0, 8.0, 9.0))) + + # TODO how to test pickle failure?! direct __setstate__ doesn't work :/ + def test_properties(self): a = Matrix3.translation(Vector2.y_axis(-5.0))@Matrix3.rotation(Deg(45.0)) self.assertEqual(a.right, Vector2(0.707107, 0.707107)) @@ -1074,6 +1125,18 @@ class Matrix4_(unittest.TestCase): (9.0, 10.0, 11.0, 12.0), (13.0, 14.0, 15.0, 16.0))) + def test_pickle(self): + data = pickle.dumps(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(pickle.loads(data), 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))) + + # TODO how to test pickle failure?! direct __setstate__ doesn't work :/ + 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)) @@ -1162,6 +1225,12 @@ class Quaternion_(unittest.TestCase): self.assertEqual(a, Quaternion((0.382683, 0.0, 0.0), 0.92388)) self.assertEqual(a.to_matrix(), Matrix4.rotation_x(Deg(45.0)).rotation_scaling()) + def test_pickle(self): + data = pickle.dumps(Quaternion((1.0, 2.0, 3.0), 4.0)) + self.assertEqual(pickle.loads(data), Quaternion((1.0, 2.0, 3.0), 4.0)) + + # TODO how to test pickle failure?! direct __setstate__ doesn't work :/ + def test_methods(self): a = Quaternion.rotation(Deg(45.0), Vector3.x_axis()) self.assertEqual(a.inverted(), @@ -1236,6 +1305,12 @@ class Range(unittest.TestCase): self.assertEqual(c.min, Vector2(3.0, 1.0)) self.assertEqual(c.max, Vector2(5.0, 1.5)) + def test_pickle(self): + data = pickle.dumps(Range2D((1.0, 0.3), (2.0, 0.6))) + self.assertEqual(pickle.loads(data), Range2D((1.0, 0.3), (2.0, 0.6))) + + # TODO how to test pickle failure?! direct __setstate__ doesn't work :/ + def test_properties(self): a = Range2D((1.0, 0.2), (2.0, 0.4)) self.assertEqual(a.bottom_right, Vector2(2.0, 0.2))