diff --git a/source/blender/python/mathutils/mathutils_Vector.c b/source/blender/python/mathutils/mathutils_Vector.c index 19246978cbf..3dc953e22e9 100644 --- a/source/blender/python/mathutils/mathutils_Vector.c +++ b/source/blender/python/mathutils/mathutils_Vector.c @@ -800,6 +800,36 @@ static PyObject *Vector_to_track_quat(VectorObject *self, PyObject *args) return Quaternion_CreatePyObject(quat, Py_NEW, NULL); } +PyDoc_STRVAR(Vector_orthogonal_doc, +".. method:: orthogonal()\n" +"\n" +" Return a perpendicular vector.\n" +"\n" +" :return: a new vector 90 degrees from this vector.\n" +" :rtype: :class:`Vector`\n" +"\n" +" .. note:: the axis is undefined, only use when any orthogonal vector is acceptable.\n" +); +static PyObject *Vector_orthogonal(VectorObject *self) +{ + float vec[3]; + + if (self->size != 3) { + PyErr_SetString(PyExc_TypeError, + "Vector.orthogonal(): " + "Vector must be 3D"); + return NULL; + } + + if (BaseMath_ReadCallback(self) == -1) + return NULL; + + ortho_v3_v3(vec, self->vec); + + return Vector_CreatePyObject(vec, self->size, Py_NEW, Py_TYPE(self)); +} + + /* * Vector.reflect(mirror): return a reflected vector on the mirror normal * vec - ((2 * DotVecs(vec, mirror)) * mirror) @@ -2768,6 +2798,7 @@ static struct PyMethodDef Vector_methods[] = { {"resize_4d", (PyCFunction) Vector_resize_4d, METH_NOARGS, Vector_resize_4d_doc}, {"to_tuple", (PyCFunction) Vector_to_tuple, METH_VARARGS, Vector_to_tuple_doc}, {"to_track_quat", (PyCFunction) Vector_to_track_quat, METH_VARARGS, Vector_to_track_quat_doc}, + {"orthogonal", (PyCFunction) Vector_orthogonal, METH_NOARGS, Vector_orthogonal_doc}, /* operation between 2 or more types */ {"reflect", (PyCFunction) Vector_reflect, METH_O, Vector_reflect_doc}, diff --git a/source/tests/bl_pyapi_mathutils.py b/source/tests/bl_pyapi_mathutils.py index bbb4d7f355f..45d68257559 100644 --- a/source/tests/bl_pyapi_mathutils.py +++ b/source/tests/bl_pyapi_mathutils.py @@ -3,6 +3,32 @@ import unittest from test import support from mathutils import Matrix, Vector from mathutils import kdtree +import math + +# keep globals immutable +vector_data = ( + (1.0, 0.0, 0.0), + (0.0, 1.0, 0.0), + (0.0, 0.0, 1.0), + + (1.0, 1.0, 1.0), + + (0.33783, 0.715698, -0.611206), + (-0.944031, -0.326599, -0.045624), + (-0.101074, -0.416443, -0.903503), + (0.799286, 0.49411, -0.341949), + (-0.854645, 0.518036, 0.033936), + (0.42514, -0.437866, -0.792114), + (-0.358948, 0.597046, 0.717377), + (-0.985413,0.144714, 0.089294), + ) + +# get data at different scales +vector_data = sum( + (tuple(tuple(a * scale for a in v) for v in vector_data) + for scale in (s * sign for s in (0.0001, 0.1, -1.0, 10.0, 1000.0, 100000.0) + for sign in (1.0, -1.0))), ()) + ((0.0, 0.0, 0.0),) + class MatrixTesting(unittest.TestCase): def test_matrix_column_access(self): @@ -149,6 +175,17 @@ class MatrixTesting(unittest.TestCase): self.assertEqual(mat * mat, prod_mat) +class VectorTesting(unittest.TestCase): + + def test_orthogonal(self): + + angle_90d = math.pi / 2.0 + for v in vector_data: + v = Vector(v) + if v.length_squared != 0.0: + self.assertAlmostEqual(v.angle(v.orthogonal()), angle_90d) + + class KDTreeTesting(unittest.TestCase): @staticmethod @@ -256,6 +293,7 @@ class KDTreeTesting(unittest.TestCase): def test_main(): try: support.run_unittest(MatrixTesting) + support.run_unittest(VectorTesting) support.run_unittest(KDTreeTesting) except: import traceback