diff --git a/buildconfig/stubs/pygame/geometry.pyi b/buildconfig/stubs/pygame/geometry.pyi index f7c1edf517..37c4d771f9 100644 --- a/buildconfig/stubs/pygame/geometry.pyi +++ b/buildconfig/stubs/pygame/geometry.pyi @@ -168,6 +168,8 @@ class Line: def b(self) -> Tuple[float, float]: ... @b.setter def b(self, value: Point) -> None: ... + @property + def length(self) -> float: ... @overload def __init__(self, ax: float, ay: float, bx: float, by: float) -> None: ... @overload @@ -176,3 +178,27 @@ class Line: def __init__(self, line: _LineLike) -> None: ... def __copy__(self) -> Line: ... def copy(self) -> Line: ... + @overload + def update(self, ax: float, ay: float, bx: float, by: float, /) -> None: ... + @overload + def update(self, a: Point, b: Point, /) -> None: ... + @overload + def update(self, line: _LineLike, /) -> None: ... + @overload + def move(self, x: float, y: float, /) -> Line: ... + @overload + def move(self, move_by: Point, /) -> Line: ... + @overload + def move_ip(self, x: float, y: float, /) -> None: ... + @overload + def move_ip(self, move_by: Point, /) -> None: ... + @overload + def scale(self, factor: float, origin: float, /) -> Line: ... + @overload + def scale(self, factor_and_origin: Tuple[float, float], /) -> Line: ... + @overload + def scale_ip(self, factor: float, origin: float, /) -> None: ... + @overload + def scale_ip(self, factor_and_origin: Tuple[float, float], /) -> None: ... + def flip_ab(self) -> Line: ... + def flip_ab_ip(self) -> None: ... diff --git a/docs/reST/ref/geometry.rst b/docs/reST/ref/geometry.rst index c6b3b99021..12ffc5ec9f 100644 --- a/docs/reST/ref/geometry.rst +++ b/docs/reST/ref/geometry.rst @@ -98,7 +98,7 @@ | :sl:`x and y coordinates of the center of the circle` | :sg:`center -> (float, float)` - It's a tuple containing the circle's `x` and `y` coordinates representing its center. + A tuple containing the circle's `x` and `y` coordinates representing its center. Reassigning it moves the circle to the new position. .. versionadded:: 2.4.0 @@ -147,7 +147,7 @@ | :sl:`top coordinate of the circle` | :sg:`top -> (float, float)` - It's a tuple containing the `x` and `y` coordinates that represent the top + A tuple containing the `x` and `y` coordinates that represent the top of the circle. Reassigning it moves the circle to the new position. The radius will not be affected. @@ -160,7 +160,7 @@ | :sl:`bottom coordinate of the circle` | :sg:`bottom -> (float, float)` - It's a tuple containing the `x` and `y` coordinates that represent the bottom + A tuple containing the `x` and `y` coordinates that represent the bottom of the circle. Reassigning it moves the circle to the new position. The radius will not be affected. @@ -173,7 +173,7 @@ | :sl:`left coordinate of the circle` | :sg:`left -> (float, float)` - It's a tuple containing the `x` and `y` coordinates that represent the left + A tuple containing the `x` and `y` coordinates that represent the left of the circle. Reassigning it moves the circle to the new position. The radius will not be affected. @@ -186,7 +186,7 @@ | :sl:`right coordinate of the circle` | :sg:`right -> (float, float)` - It's a tuple containing the `x` and `y` coordinates that represent the right + A tuple containing the `x` and `y` coordinates that represent the right of the circle. Reassigning it moves the circle to the new position. The radius will not be affected. @@ -574,7 +574,7 @@ | :sl:`the first point of the line` | :sg:`a -> (float, float)` - It's a tuple containing the `ax` and `ay` attributes representing the line's first point. + A tuple containing the `ax` and `ay` attributes representing the line's first point. It can be reassigned to move the `Line`. If reassigned the `ax` and `ay` attributes will be changed to produce a `Line` with matching first point position. The `bx` and `by` attributes will not be affected. @@ -588,7 +588,7 @@ | :sl:`the second point of the line` | :sg:`b -> (float, float)` - It's a tuple containing `bx` and `by` attributes representing the line's second point. + A tuple containing `bx` and `by` attributes representing the line's second point. It can be reassigned to move the `Line`. If reassigned the `bx` and `by` attributes will be changed to produce a `Line` with matching second point position. The `ax` and `ay` attributes will not be affected. @@ -597,6 +597,19 @@ .. ## Line.b ## + .. attribute:: length + + | :sl:`the length of the line` + | :sg:`length -> float` + + The length of the line. Calculated using the `sqrt((bx-ax)**2 + (by-ay)**2)` formula. + This attribute is read-only, it cannot be reassigned. To change the line's length + use the `scale` method or change its `a` or `b` attributes. + + .. versionadded:: 2.5.3 + + .. ## Line.length ## + **Line Methods** ---- @@ -611,3 +624,119 @@ .. versionadded:: 2.5.2 .. ## Line.copy ## + + .. method:: move + + | :sl:`moves the line by a given amount` + | :sg:`move((x, y)) -> Line` + | :sg:`move(x, y) -> Line` + + Returns a new `Line` that is moved by the given offset. The original `Line` is + not modified. + + This method is equivalent to the following code: + + .. code-block:: python + + Line(line.ax + x, line.ay + y, line.bx + x, line.by + y) + + .. versionadded:: 2.5.3 + + .. ## Line.move ## + + .. method:: move_ip + + | :sl:`moves the line by a given amount` + | :sg:`move_ip((x, y)) -> None` + | :sg:`move_ip(x, y) -> None` + + Moves the `Line` by the given offset. The original `Line` is modified. Always returns + `None`. + + This method is equivalent to the following code: + + .. code-block:: python + + line.ax += x + line.ay += y + line.bx += x + line.by += y + + .. versionadded:: 2.5.3 + + .. ## Line.move_ip ## + + .. method:: update + + | :sl:`updates the line's attributes` + | :sg:`update((ax, ay), (bx, by)) -> None` + | :sg:`update(ax, ay, bx, by) -> None` + | :sg:`update(line) -> None` + + Updates the `Line`'s attributes. The original `Line` is modified. Always returns `None`. + + This method is equivalent to the following code: + + .. code-block:: python + + line.ax = ax + line.ay = ay + line.bx = bx + line.by = by + + .. versionadded:: 2.5.3 + + .. ## Line.update ## + + .. method:: scale + + | :sl:`scales the line by the given factor from the given origin` + | :sg:`scale(factor, origin) -> Line` + | :sg:`scale(factor_and_origin) -> Line` + + Returns a new `Line` which is scaled by the given factor from the specified origin with 0.0 being + the starting point, 0.5 being the center and 1.0 being the end point. + The original `Line` is not modified. + + .. versionadded:: 2.5.3 + + .. ## Line.scale ## + + .. method:: scale_ip + + | :sl:`scales the line by the given factor from the given origin in place` + | :sg:`scale_ip(factor, origin) -> None` + | :sg:`scale_ip(factor_and_origin) -> None` + + Scales the `Line` by the given factor from the specified origin with 0.0 being + the starting point, 0.5 being the center and 1.0 being the end point. + The original `Line` is modified. + Always returns `None`. + + .. versionadded:: 2.5.3 + + .. ## Line.scale_ip ## + + .. method:: flip_ab + + | :sl:`flips the line a and b points` + | :sg:`flip_ab() -> Line` + + Returns a new `Line` that has the `a` and `b` points flipped. + The original `Line` is not modified. + + .. versionadded:: 2.5.3 + + .. ## Line.flip_ab ## + + .. method:: flip_ab_ip + + | :sl:`flips the line a and b points, in place` + | :sg:`flip_ab_ip() -> None` + + Flips the `Line`'s `a` and `b` points. The original `Line` is modified. + Always returns `None`. + + .. versionadded:: 2.5.3 + + .. ## Line.flip_ab_ip ## diff --git a/src_c/doc/geometry_doc.h b/src_c/doc/geometry_doc.h index dc2b2a0f1a..7873df226f 100644 --- a/src_c/doc/geometry_doc.h +++ b/src_c/doc/geometry_doc.h @@ -36,4 +36,12 @@ #define DOC_LINE_BY "by -> float\ny coordinate of the end point of the line" #define DOC_LINE_A "a -> (float, float)\nthe first point of the line" #define DOC_LINE_B "b -> (float, float)\nthe second point of the line" +#define DOC_LINE_LENGTH "length -> float\nthe length of the line" #define DOC_LINE_COPY "copy() -> Line\ncopies the line" +#define DOC_LINE_MOVE "move((x, y)) -> Line\nmove(x, y) -> Line\nmoves the line by a given amount" +#define DOC_LINE_MOVEIP "move_ip((x, y)) -> None\nmove_ip(x, y) -> None\nmoves the line by a given amount" +#define DOC_LINE_UPDATE "update((ax, ay), (bx, by)) -> None\nupdate(ax, ay, bx, by) -> None\nupdate(line) -> None\nupdates the line's attributes" +#define DOC_LINE_SCALE "scale(factor, origin) -> Line\nscale(factor_and_origin) -> Line\nscales the line by the given factor from the given origin" +#define DOC_LINE_SCALEIP "scale_ip(factor, origin) -> None\nscale_ip(factor_and_origin) -> None\nscales the line by the given factor from the given origin in place" +#define DOC_LINE_FLIPAB "flip_ab() -> Line\nflips the line a and b points" +#define DOC_LINE_FLIPABIP "flip_ab_ip() -> None\nflips the line a and b points, in place" diff --git a/src_c/geometry_common.c b/src_c/geometry_common.c index 4e25c32d9e..7d426fc0b0 100644 --- a/src_c/geometry_common.c +++ b/src_c/geometry_common.c @@ -162,29 +162,23 @@ pgLine_FromObject(PyObject *obj, pgLineBase *out) length = PySequence_Fast_GET_SIZE(obj); PyObject **farray = PySequence_Fast_ITEMS(obj); - if (length == 4) { - if (!pg_DoubleFromObj(farray[0], &out->ax) || - !pg_DoubleFromObj(farray[1], &out->ay) || - !pg_DoubleFromObj(farray[2], &out->bx) || - !pg_DoubleFromObj(farray[3], &out->by)) { - return 0; - } - return 1; - } - else if (length == 2) { - if (!pg_TwoDoublesFromObj(farray[0], &out->ax, &out->ay) || - !pg_TwoDoublesFromObj(farray[1], &out->bx, &out->by)) { - PyErr_Clear(); + switch (length) { + case 4: + return pg_DoubleFromObj(farray[0], &out->ax) && + pg_DoubleFromObj(farray[1], &out->ay) && + pg_DoubleFromObj(farray[2], &out->bx) && + pg_DoubleFromObj(farray[3], &out->by); + case 2: + return pg_TwoDoublesFromObj(farray[0], &out->ax, &out->ay) && + pg_TwoDoublesFromObj(farray[1], &out->bx, &out->by); + case 1: /*looks like an arg?*/ + if (PyUnicode_Check(farray[0]) || + !pgLine_FromObject(farray[0], out)) { + return 0; + } + return 1; + default: return 0; - } - return 1; - } - else if (length == 1) /*looks like an arg?*/ { - if (PyUnicode_Check(farray[0]) || - !pgLine_FromObject(farray[0], out)) { - return 0; - } - return 1; } } else if (PySequence_Check(obj)) { @@ -276,6 +270,26 @@ pgLine_FromObject(PyObject *obj, pgLineBase *out) return 1; } +int +pgLine_FromObjectFastcall(PyObject *const *args, Py_ssize_t nargs, + pgLineBase *out) +{ + switch (nargs) { + case 1: + return pgLine_FromObject(args[0], out); + case 2: + return pg_TwoDoublesFromObj(args[0], &out->ax, &out->ay) && + pg_TwoDoublesFromObj(args[1], &out->bx, &out->by); + case 4: + return pg_DoubleFromObj(args[0], &out->ax) && + pg_DoubleFromObj(args[1], &out->ay) && + pg_DoubleFromObj(args[2], &out->bx) && + pg_DoubleFromObj(args[3], &out->by); + default: + return 0; + } +} + static inline int double_compare(double a, double b) { diff --git a/src_c/geometry_common.h b/src_c/geometry_common.h index 8ecc9b259a..9bff7f242a 100644 --- a/src_c/geometry_common.h +++ b/src_c/geometry_common.h @@ -16,6 +16,10 @@ pgCircle_FromObjectFastcall(PyObject *const *args, Py_ssize_t nargs, int pgLine_FromObject(PyObject *obj, pgLineBase *out); +int +pgLine_FromObjectFastcall(PyObject *const *args, Py_ssize_t nargs, + pgLineBase *out); + static inline int double_compare(double a, double b); diff --git a/src_c/line.c b/src_c/line.c index 93987e946d..2b61906fbf 100644 --- a/src_c/line.c +++ b/src_c/line.c @@ -1,6 +1,14 @@ #include "doc/geometry_doc.h" #include "geometry_common.h" +static double +pgLine_Length(pgLineBase *line) +{ + double dx = line->bx - line->ax; + double dy = line->by - line->ay; + return sqrt(dx * dx + dy * dy); +} + static PyObject * _pg_line_subtype_new4(PyTypeObject *type, double ax, double ay, double bx, double by) @@ -40,7 +48,7 @@ pg_line_dealloc(pgLineObject *self) static int pg_line_init(pgLineObject *self, PyObject *args, PyObject *kwds) { - if (!pgLine_FromObject(args, &(self->line))) { + if (!pgLine_FromObject(args, &self->line)) { PyErr_SetString(PyExc_TypeError, "Invalid line end points, expected 4 " "numbers or 2 sequences of 2 numbers"); @@ -49,6 +57,12 @@ pg_line_init(pgLineObject *self, PyObject *args, PyObject *kwds) return 0; } +static PyObject * +pgLine_New(pgLineBase *l) +{ + return _pg_line_subtype_new4(&pgLine_Type, l->ax, l->ay, l->bx, l->by); +} + static PyObject * pg_line_copy(pgLineObject *self, PyObject *_null) { @@ -56,9 +70,166 @@ pg_line_copy(pgLineObject *self, PyObject *_null) self->line.bx, self->line.by); } +static PyObject * +pg_line_update(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + if (!pgLine_FromObjectFastcall(args, nargs, &self->line)) { + return RAISE(PyExc_TypeError, + "Line.update requires a line or LineLike object"); + } + Py_RETURN_NONE; +} + +static PyObject * +pg_line_move(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + double Dx, Dy; + + if (!pg_TwoDoublesFromFastcallArgs(args, nargs, &Dx, &Dy)) { + return RAISE(PyExc_TypeError, "move requires a pair of numbers"); + } + + return _pg_line_subtype_new4(Py_TYPE(self), self->line.ax + Dx, + self->line.ay + Dy, self->line.bx + Dx, + self->line.by + Dy); +} + +static PyObject * +pg_line_move_ip(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + double Dx, Dy; + + if (!pg_TwoDoublesFromFastcallArgs(args, nargs, &Dx, &Dy)) { + return RAISE(PyExc_TypeError, "move_ip requires a pair of numbers"); + } + + self->line.ax += Dx; + self->line.ay += Dy; + self->line.bx += Dx; + self->line.by += Dy; + + Py_RETURN_NONE; +} + +static PyObject * +pg_line_flip(pgLineObject *self, PyObject *_null) +{ + return _pg_line_subtype_new4(Py_TYPE(self), self->line.bx, self->line.by, + self->line.ax, self->line.ay); +} + +static PyObject * +pg_line_flip_ab_ip(pgLineObject *self, PyObject *_null) +{ + double tx = self->line.bx; + double ty = self->line.by; + + self->line.bx = self->line.ax; + self->line.by = self->line.ay; + + self->line.ax = tx; + self->line.ay = ty; + + Py_RETURN_NONE; +} + +static PG_FORCEINLINE double +_lerp_helper(double start, double end, double amount) +{ + return start + (end - start) * amount; +} + +static int +_line_scale_helper(pgLineBase *line, double factor, double origin) +{ + if (factor == 1.0) { + return 1; + } + else if (factor <= 0.0) { + PyErr_SetString(PyExc_ValueError, + "Can only scale by a positive non zero number"); + return 0; + } + + if (origin < 0.0 || origin > 1.0) { + PyErr_SetString(PyExc_ValueError, "Origin must be between 0 and 1"); + return 0; + } + + double ax = line->ax; + double ay = line->ay; + double bx = line->bx; + double by = line->by; + + double x1_factor = ax * factor; + double y1_factor = ay * factor; + double x2_factor = bx * factor; + double y2_factor = by * factor; + + double fac_m_one = factor - 1; + double dx = _lerp_helper(fac_m_one * ax, fac_m_one * bx, origin); + double dy = _lerp_helper(fac_m_one * ay, fac_m_one * by, origin); + + line->ax = x1_factor - dx; + line->ay = y1_factor - dy; + line->bx = x2_factor - dx; + line->by = y2_factor - dy; + + return 1; +} + +static PyObject * +pg_line_scale(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + double factor, origin; + + if (!pg_TwoDoublesFromFastcallArgs(args, nargs, &factor, &origin)) { + return RAISE(PyExc_TypeError, + "scale requires a sequence of two numbers"); + } + + PyObject *line; + if (!(line = pgLine_New(&self->line))) { + return NULL; + } + + if (!_line_scale_helper(&pgLine_AsLine(line), factor, origin)) { + Py_DECREF(line); + return NULL; + } + + return line; +} + +static PyObject * +pg_line_scale_ip(pgLineObject *self, PyObject *const *args, Py_ssize_t nargs) +{ + double factor, origin; + + if (!pg_TwoDoublesFromFastcallArgs(args, nargs, &factor, &origin)) { + return RAISE(PyExc_TypeError, + "scale_ip requires a sequence of two numbers"); + } + + if (!_line_scale_helper(&pgLine_AsLine(self), factor, origin)) { + return NULL; + } + + Py_RETURN_NONE; +} + static struct PyMethodDef pg_line_methods[] = { {"__copy__", (PyCFunction)pg_line_copy, METH_NOARGS, DOC_LINE_COPY}, {"copy", (PyCFunction)pg_line_copy, METH_NOARGS, DOC_LINE_COPY}, + {"update", (PyCFunction)pg_line_update, METH_FASTCALL, DOC_LINE_UPDATE}, + {"move", (PyCFunction)pg_line_move, METH_FASTCALL, DOC_LINE_MOVE}, + {"move_ip", (PyCFunction)pg_line_move_ip, METH_FASTCALL, DOC_LINE_MOVEIP}, + {"flip_ab", (PyCFunction)pg_line_flip, METH_NOARGS, DOC_LINE_FLIPAB}, + {"flip_ab_ip", (PyCFunction)pg_line_flip_ab_ip, METH_NOARGS, + DOC_LINE_FLIPABIP}, + {"scale", (PyCFunction)pg_line_scale, METH_FASTCALL, DOC_LINE_SCALE}, + {"scale_ip", (PyCFunction)pg_line_scale_ip, METH_FASTCALL, + DOC_LINE_SCALEIP}, {NULL, NULL, 0, NULL}}; static PyObject * @@ -170,6 +341,12 @@ pg_line_setb(pgLineObject *self, PyObject *value, void *closure) return -1; } +static PyObject * +pg_line_getlength(pgLineObject *self, void *closure) +{ + return PyFloat_FromDouble(pgLine_Length(&self->line)); +} + static PyGetSetDef pg_line_getsets[] = { {"ax", (getter)pg_line_getax, (setter)pg_line_setax, DOC_LINE_AX, NULL}, {"ay", (getter)pg_line_getay, (setter)pg_line_setay, DOC_LINE_AY, NULL}, @@ -177,6 +354,7 @@ static PyGetSetDef pg_line_getsets[] = { {"by", (getter)pg_line_getby, (setter)pg_line_setby, DOC_LINE_BY, NULL}, {"a", (getter)pg_line_geta, (setter)pg_line_seta, DOC_LINE_A, NULL}, {"b", (getter)pg_line_getb, (setter)pg_line_setb, DOC_LINE_B, NULL}, + {"length", (getter)pg_line_getlength, NULL, DOC_LINE_LENGTH, NULL}, {NULL, 0, NULL, NULL, NULL}}; static PyTypeObject pgLine_Type = { diff --git a/test/geometry_test.py b/test/geometry_test.py index d2b5fceb50..445d57e2af 100644 --- a/test/geometry_test.py +++ b/test/geometry_test.py @@ -1967,6 +1967,56 @@ def test_attrib_b(self): with self.assertRaises(AttributeError): del line.b + def test_attrib_length(self): + """a full test for the length attribute""" + expected_length = 3.0 + line = Line(1, 4, 4, 4) + self.assertEqual(line.length, expected_length) + + line.ax = 2 + expected_length = 2.0 + self.assertEqual(line.length, expected_length) + + line.ax = 2.7 + expected_length = 1.2999999999999998 + self.assertAlmostEqual(line.length, expected_length) + + line.ay = 2 + expected_length = 2.3853720883753127 + self.assertAlmostEqual(line.length, expected_length) + + line.ay = 2.7 + expected_length = 1.8384776310850233 + self.assertAlmostEqual(line.length, expected_length) + + line.bx = 2 + expected_length = 1.4764823060233399 + self.assertAlmostEqual(line.length, expected_length) + + line.bx = 2.7 + expected_length = 1.2999999999999998 + self.assertAlmostEqual(line.length, expected_length) + + line.by = 2 + expected_length = 0.7000000000000002 + self.assertAlmostEqual(line.length, expected_length) + + line.by = 2.7 + expected_length = 0.0 + self.assertEqual(line.length, expected_length) + + line1 = Line(7, 3, 2, 3) + line2 = Line(9, 5, 4, 5) + self.assertEqual(line1.length, line2.length) + + line = Line(7.6, 3.2, 2.1, 3.8) + expected_length = 5.532630477449222 + self.assertAlmostEqual(line.length, expected_length) + + line = Line(-9.8, -5.2, -4.4, -5.6) + expected_length = 5.414794548272353 + self.assertAlmostEqual(line.length, expected_length) + def test_meth_copy(self): line = Line(1, 2, 3, 4) # check 1 arg passed @@ -1981,6 +2031,176 @@ def test_meth_copy(self): self.assertIsNot(line, line_2) + def test_meth_move(self): + line = Line(1.1, 2.2, 3.3, 4.4) + + ret = line.move(1, 2) + + self.assertEqual(ret.ax, 2.1) + self.assertEqual(ret.ay, 4.2) + self.assertEqual(ret.bx, 4.3) + self.assertEqual(ret.by, 6.4) + + with self.assertRaises(TypeError): + line.move() + + with self.assertRaises(TypeError): + line.move(1) + + with self.assertRaises(TypeError): + line.move(1, 2, 3) + + with self.assertRaises(TypeError): + line.move("1", "2") + + def test_meth_move_ip(self): + line = Line(1.1, 2.2, 3.3, 4.4) + + line.move_ip(1, 2) + + self.assertEqual(line.ax, 2.1) + self.assertEqual(line.ay, 4.2) + self.assertEqual(line.bx, 4.3) + self.assertEqual(line.by, 6.4) + + with self.assertRaises(TypeError): + line.move_ip() + + with self.assertRaises(TypeError): + line.move_ip(1) + + with self.assertRaises(TypeError): + line.move_ip(1, 2, 3) + + with self.assertRaises(TypeError): + line.move_ip("1", "2") + + def test_meth_scale(self): + line = Line(0, 0, 10, 0).scale(2, 0) + self.assertEqual(line.length, 20) + line = Line(0, 0, 20, 0).scale(2.1, 0) + self.assertEqual(line.length, 42) + line = Line(0, 0, 10, 0).scale(4, 0) + self.assertEqual(line.length, 40) + line = Line(0, 0, 10, 0).scale(3, 0) + self.assertEqual(line.length, 30) + line = Line(10, 10, 20, 20).scale(2, 0) + self.assertAlmostEqual(line.length, 28.284271247461902) + line = Line(10, 10, 20, 20).scale(2, 0.5) + self.assertAlmostEqual(line.length, 28.284271247461902) + line = Line(10, 10, 20, 20).scale(2, 1) + self.assertAlmostEqual(line.length, 28.284271247461902) + + with self.assertRaises(ValueError): + line = line.scale(0, 0.5) + + with self.assertRaises(ValueError): + line = line.scale(2, -0.1) + + with self.assertRaises(ValueError): + line = line.scale(-2, -0.5) + + with self.assertRaises(ValueError): + line = line.scale(17, 1.1) + + with self.assertRaises(ValueError): + line = line.scale(17, 10.0) + + def test_meth_scale_ip(self): + line = Line(0, 0, 10, 0) + line.scale_ip(2, 0) + self.assertEqual(line.length, 20) + line = Line(0, 0, 20, 0) + line.scale_ip(2.1, 0) + self.assertEqual(line.length, 42) + line = Line(0, 0, 10, 0) + line.scale_ip(4, 0) + self.assertEqual(line.length, 40) + line = Line(0, 0, 10, 0) + line.scale_ip(3, 0) + self.assertEqual(line.length, 30) + line = Line(10, 10, 20, 20) + line.scale_ip(2, 0) + self.assertAlmostEqual(line.length, 28.284271247461902) + line = Line(10, 10, 20, 20) + line.scale_ip(2, 0.5) + self.assertAlmostEqual(line.length, 28.284271247461902) + line = Line(10, 10, 20, 20) + line.scale_ip(2, 1.0) + self.assertAlmostEqual(line.length, 28.284271247461902) + + with self.assertRaises(ValueError): + line.scale_ip(0, 0.5) + + with self.assertRaises(ValueError): + line.scale_ip(2, -0.1) + + with self.assertRaises(ValueError): + line.scale_ip(-2, -0.5) + + with self.assertRaises(ValueError): + line.scale_ip(17, 1.1) + + with self.assertRaises(ValueError): + line.scale_ip(17, 10.0) + + def test_meth_flip(self): + line = Line(1.1, 2.2, 3.3, 4.4) + + ret = line.flip_ab() + + self.assertIsInstance(ret, Line) + self.assertEqual(ret.ax, 3.3) + self.assertEqual(ret.ay, 4.4) + self.assertEqual(ret.bx, 1.1) + self.assertEqual(ret.by, 2.2) + + with self.assertRaises(TypeError): + line.flip_ab(1) + + def test_meth_flip_ab_ip(self): + line = Line(1.1, 2.2, 3.3, 4.4) + + line.flip_ab_ip() + + self.assertEqual(line.ax, 3.3) + self.assertEqual(line.ay, 4.4) + self.assertEqual(line.bx, 1.1) + self.assertEqual(line.by, 2.2) + + with self.assertRaises(TypeError): + line.flip_ab_ip(1) + + def test_meth_update(self): + line = Line(0, 0, 1, 1) + + line.update(1, 2, 3, 4) + self.assertEqual(line.ax, 1) + self.assertEqual(line.ay, 2) + self.assertEqual(line.bx, 3) + self.assertEqual(line.by, 4) + + line.update((5, 6), (7, 8)) + self.assertEqual(line.ax, 5) + self.assertEqual(line.ay, 6) + self.assertEqual(line.bx, 7) + self.assertEqual(line.by, 8) + + line.update((9, 10, 11, 12)) + self.assertEqual(line.ax, 9) + self.assertEqual(line.ay, 10) + self.assertEqual(line.bx, 11) + self.assertEqual(line.by, 12) + + with self.assertRaises(TypeError): + line.update() + + with self.assertRaises(TypeError): + line.update(1, 2, 3, 4, 5) + + with self.assertRaises(TypeError): + line.update(1, 2, 3) + def test__str__(self): """Checks whether the __str__ method works correctly.""" l_str = ""