Browse Source

python: implemented vector swizzles.

pull/8/head
Vladimír Vondruš 7 years ago
parent
commit
d818e40698
  1. 18
      doc/python/magnum.math.rst
  2. 78
      src/python/magnum/math.vector.h
  3. 60
      src/python/magnum/test/test_math.py

18
doc/python/magnum.math.rst

@ -192,6 +192,17 @@
- All vector and matrix classes implement :py:`len()`, which is used - All vector and matrix classes implement :py:`len()`, which is used
instead of e.g. :dox:`Math::Vector::Size`. Works on both classes instead of e.g. :dox:`Math::Vector::Size`. Works on both classes
and instances. and instances.
- :cpp:`Math::gather()` and :cpp:`Math::scatter()` operations are
implemented as real swizzles:
.. code:: pycon
>>> a = Vector4(1.5, 0.3, -1.0, 1.0)
>>> b = Vector4(7.2, 2.3, 1.1, 0.0)
>>> a.wxy = b.xwz
>>> a
Vector(0, 1.1, -1, 7.2)
- :py:`mat[a][b] = c` on matrices doesn't do the expected thing, use - :py:`mat[a][b] = c` on matrices doesn't do the expected thing, use
:py:`mat[a, b] = c` instead :py:`mat[a, b] = c` instead
- :cpp:`Math::BoolVector::set()` doesn't exist, use ``[]`` instead - :cpp:`Math::BoolVector::set()` doesn't exist, use ``[]`` instead
@ -200,13 +211,6 @@
possible to do in Python. Here the boolean operations behave like possible to do in Python. Here the boolean operations behave like
if :py:`any()` was applied before doing the operation. if :py:`any()` was applied before doing the operation.
.. block-warning:: Subject to change
The :dox:`Math::swizzle()` operation is not yet available in the Python
API. Thanks to better flexibility of the Python language this will get
implemented as a *real* swizzle, allowing for convenient expressions
like :py:`vec.xz = (3.5, 0.1)`.
`Static constructors and instance method / property overloads`_ `Static constructors and instance method / property overloads`_
--------------------------------------------------------------- ---------------------------------------------------------------

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

@ -228,6 +228,84 @@ template<class T> void vector(py::module& m, py::class_<T>& c) {
return self[i]; return self[i];
}, "Value at given position") }, "Value at given position")
/* Swizzle */
/* TODO: both of these could be *way* more efficiently implemented
directly on PyObject (no need to throw, no need to do string
conversions...) but then these wouldn't be visible to docs I fear */
.def("__getattr__", [](T& self, const std::string& name) -> py::object {
if(name.size() > 4) {
PyErr_SetString(PyExc_AttributeError, "only four-component swizzles are supported at most");
throw pybind11::error_already_set{};
}
Math::Vector4<typename T::Type> out;
for(std::size_t i = 0; i != name.size(); ++i) {
if(name[i] == 'x' || name[i] == 'r') out[i] = self[0];
else if(name[i] == 'y' || name[i] == 'g') out[i] = self[1];
else if(T::Size > 2 && (name[i] == 'z' || name[i] == 'b')) out[i] = self[2];
else if(T::Size > 3 && (name[i] == 'w' || name[i] == 'a')) out[i] = self[3];
else {
PyErr_SetString(PyExc_AttributeError, "invalid swizzle");
throw pybind11::error_already_set{};
}
}
if(name.size() == 4) return py::cast(out);
else if(name.size() == 3) return py::cast(out.xyz());
else if(name.size() == 2) return py::cast(out.xy());
/* this should be handled by the x/y/z/w/r/g/b/a properties instead */
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}, "Vector swizzle")
.def("__setattr__", [](T& self, py::str nameO, py::object valueO) {
std::string name = py::cast<std::string>(nameO);
/* If the name is just one character, this is better handled by
dedicated properties (and if not, it'll provide a better
diagnostic than we can). Same for xy / xyz / ... when
applicable, and when the name contains non-swizzle characters */
if(name.size() == 1 ||
(name.compare("xy") == 0 && T::Size > 2) ||
(name.compare("xyz") == 0 && T::Size > 3) ||
(name.compare("rgb") == 0 && T::Size > 3) ||
name.find_first_not_of("xyzwrgba") != std::string::npos) {
if(PySuper_Type.tp_setattro(py::cast(self).ptr(), nameO.ptr(), valueO.ptr()) != 0)
throw pybind11::error_already_set{};
return;
}
/* Here we can be certain it's a swizzle attempt, so throw clear
error messages */
const typename T::Type* data;
std::size_t size;
if(py::isinstance<Math::Vector2<typename T::Type>>(valueO)) {
data = py::cast<const Math::Vector2<typename T::Type>&>(valueO).data();
size = 2;
} else if(py::isinstance<Math::Vector3<typename T::Type>>(valueO)) {
data = py::cast<const Math::Vector3<typename T::Type>&>(valueO).data();
size = 3;
} else if(py::isinstance<Math::Vector4<typename T::Type>>(valueO)) {
data = py::cast<const Math::Vector4<typename T::Type>&>(valueO).data();
size = 4;
} else {
PyErr_SetString(PyExc_TypeError, "unrecognized swizzle type");
throw pybind11::error_already_set{};
}
if(name.size() != size) {
PyErr_SetString(PyExc_TypeError, "swizzle doesn't match passed vector component count");
throw pybind11::error_already_set{};
}
for(std::size_t i = 0; i != name.size(); ++i) {
if(name[i] == 'x' || name[i] == 'r') self[0] = data[i];
else if(name[i] == 'y' || name[i] == 'g') self[1] = data[i];
else if(T::Size > 2 && (name[i] == 'z' || name[i] == 'b')) self[2] = data[i];
else if(T::Size > 3 && (name[i] == 'w' || name[i] == 'a')) self[3] = data[i];
else {
PyErr_SetString(PyExc_AttributeError, "invalid swizzle");
throw pybind11::error_already_set{};
}
}
}, "Vector swizzle")
/* Member functions common for floating-point and integer types */ /* Member functions common for floating-point and integer types */
.def("is_zero", &T::isZero, "Whether the vector is zero") .def("is_zero", &T::isZero, "Whether the vector is zero")
.def("dot", static_cast<typename T::Type(T::*)() const>(&T::dot), "Dot product of the vector") .def("dot", static_cast<typename T::Type(T::*)() const>(&T::dot), "Dot product of the vector")

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

@ -267,6 +267,66 @@ class Vector(unittest.TestCase):
self.assertEqual(2.0*Vector2(1.0, -3.0), Vector2(2.0, -6.0)) self.assertEqual(2.0*Vector2(1.0, -3.0), Vector2(2.0, -6.0))
self.assertEqual(6.0/Vector2(2.0, -3.0), Vector2(3.0, -2.0)) self.assertEqual(6.0/Vector2(2.0, -3.0), Vector2(3.0, -2.0))
def test_swizzle(self):
self.assertEqual(Vector3(3.0, 1.5, 0.4).yzxz, Vector4(1.5, 0.4, 3.0, 0.4))
self.assertEqual(Vector3(3.0, 1.5, 0.4).gbrb, Vector4(1.5, 0.4, 3.0, 0.4))
self.assertEqual(Vector4(3.0, 1.5, 0.4, 1.2).wyx, Vector3(1.2, 1.5, 3.0))
self.assertEqual(Vector4(3.0, 1.5, 0.4, 1.2).agr, Vector3(1.2, 1.5, 3.0))
self.assertEqual(Vector2(3.0, 1.5).yx, Vector2(1.5, 3.0))
self.assertEqual(Vector2(3.0, 1.5).gr, Vector2(1.5, 3.0))
with self.assertRaisesRegex(AttributeError, "only four-component swizzles are supported at most"):
Vector4().xyzwx
with self.assertRaisesRegex(AttributeError, "invalid swizzle"):
Vector3().xyzw
with self.assertRaisesRegex(AttributeError, "invalid swizzle"):
Vector2().xyz
with self.assertRaisesRegex(AttributeError, "invalid swizzle"):
Vector4().c
def test_swizzle_set(self):
a1 = Vector3(3.0, 1.5, 0.4)
a2 = Vector3(3.0, 1.5, 0.4)
a1.zy = Vector2(0.5, 1.3)
a2.bg = Vector2(0.5, 1.3)
self.assertEqual(a1, Vector3(3.0, 1.3, 0.5))
self.assertEqual(a1, Vector3(3.0, 1.3, 0.5))
b1 = Vector4(3.0, 1.5, 0.4, 1.2)
b2 = Vector4(3.0, 1.5, 0.4, 1.2)
b1.wxz = Vector3(1.1, 0.0, -1.3)
b2.arb = Vector3(1.1, 0.0, -1.3)
self.assertEqual(b1, Vector4(0.0, 1.5, -1.3, 1.1))
self.assertEqual(b2, Vector4(0.0, 1.5, -1.3, 1.1))
# Not sure if this should be supported, but also why not
c = Vector2(1.1, 0.4)
c.xyyx = Vector4(0.1, 0.2, 0.3, 0.7)
self.assertEqual(c, Vector2(0.7, 0.3))
# Passing derived types should work too
d = Vector4(3.0, 1.5, 0.4, 1.2)
d.wxz = Color3(1.1, 0.0, -1.3)
self.assertEqual(d, Vector4(0.0, 1.5, -1.3, 1.1))
# Handled by pybind / python as a fallback directly
with self.assertRaises(AttributeError):
Vector4().xc = Vector2()
with self.assertRaisesRegex(TypeError, "incompatible function arguments"):
Vector4().xyz = 3
with self.assertRaisesRegex(TypeError, "incompatible function arguments"):
Vector4().xy = 3
# Handled by the swizzle implementation
with self.assertRaisesRegex(TypeError, "unrecognized swizzle type"):
Vector4().xzy = 3
with self.assertRaisesRegex(TypeError, "swizzle doesn't match passed vector component count"):
Vector2().yx = Vector3()
with self.assertRaisesRegex(AttributeError, "invalid swizzle"):
Vector3().xyzw = Vector4()
with self.assertRaisesRegex(AttributeError, "invalid swizzle"):
Vector2().xyz = Vector3()
def test_repr(self): def test_repr(self):
self.assertEqual(repr(Vector3(1.0, 3.14, -13.37)), 'Vector(1, 3.14, -13.37)') self.assertEqual(repr(Vector3(1.0, 3.14, -13.37)), 'Vector(1, 3.14, -13.37)')

Loading…
Cancel
Save