1+ import math
12import unittest
23from math import sqrt
34
@@ -20,6 +21,15 @@ def get_points_between(line, n_pts):
2021 return [(line .xa + i * dx , line .ya + i * dy ) for i in range (n_pts + 2 )]
2122
2223
24+ def float_range (a , b , step ):
25+ result = []
26+ current_value = a
27+ while current_value < b :
28+ result .append (current_value )
29+ current_value += step
30+ return result
31+
32+
2333class LineTypeTest (unittest .TestCase ):
2434 class ClassWithLineAttrib :
2535 def __init__ (self , line ):
@@ -1381,6 +1391,187 @@ def test_meth_as_segments_argvalue(self):
13811391 with self .assertRaises (ValueError ):
13821392 l .as_segments (value )
13831393
1394+ def test_meth_rotate_ip_invalid_argnum (self ):
1395+ """Ensures that the rotate_ip method correctly deals with invalid numbers of arguments."""
1396+ l = Line (0 , 0 , 1 , 1 )
1397+
1398+ with self .assertRaises (TypeError ):
1399+ l .rotate_ip ()
1400+
1401+ invalid_args = [
1402+ (1 , (2 , 2 ), 2 ),
1403+ (1 , (2 , 2 ), 2 , 2 ),
1404+ (1 , (2 , 2 ), 2 , 2 , 2 ),
1405+ (1 , (2 , 2 ), 2 , 2 , 2 , 2 ),
1406+ (1 , (2 , 2 ), 2 , 2 , 2 , 2 , 2 ),
1407+ (1 , (2 , 2 ), 2 , 2 , 2 , 2 , 2 , 2 ),
1408+ ]
1409+
1410+ for args in invalid_args :
1411+ with self .assertRaises (TypeError ):
1412+ l .rotate_ip (* args )
1413+
1414+ def test_meth_rotate_ip_invalid_argtype (self ):
1415+ """Ensures that the rotate_ip method correctly deals with invalid argument types."""
1416+ l = Line (0 , 0 , 1 , 1 )
1417+
1418+ invalid_args = [
1419+ ("a" ,), # angle str
1420+ (None ,), # angle str
1421+ ((1 , 2 )), # angle tuple
1422+ ([1 , 2 ]), # angle list
1423+ (1 , "a" ), # origin str
1424+ (1 , None ), # origin None
1425+ (1 , True ), # origin True
1426+ (1 , False ), # origin False
1427+ (1 , (1 , 2 , 3 )), # origin tuple
1428+ (1 , [1 , 2 , 3 ]), # origin list
1429+ (1 , (1 , "a" )), # origin str
1430+ (1 , ("a" , 1 )), # origin str
1431+ (1 , (1 , None )), # origin None
1432+ (1 , (None , 1 )), # origin None
1433+ (1 , (1 , (1 , 2 ))), # origin tuple
1434+ (1 , (1 , [1 , 2 ])), # origin list
1435+ ]
1436+
1437+ for value in invalid_args :
1438+ with self .assertRaises (TypeError ):
1439+ l .rotate_ip (* value )
1440+
1441+ def test_meth_rotate_ip_return (self ):
1442+ """Ensures that the rotate_ip method always returns None."""
1443+ l = Line (0 , 0 , 1 , 1 )
1444+
1445+ for angle in float_range (- 360 , 360 , 1 ):
1446+ self .assertIsNone (l .rotate_ip (angle ))
1447+ self .assertIsInstance (l .rotate_ip (angle ), type (None ))
1448+
1449+ def test_meth_rotate_invalid_argnum (self ):
1450+ """Ensures that the rotate method correctly deals with invalid numbers of arguments."""
1451+ l = Line (0 , 0 , 1 , 1 )
1452+
1453+ with self .assertRaises (TypeError ):
1454+ l .rotate ()
1455+
1456+ invalid_args = [
1457+ (1 , (2 , 2 ), 2 ),
1458+ (1 , (2 , 2 ), 2 , 2 ),
1459+ (1 , (2 , 2 ), 2 , 2 , 2 ),
1460+ (1 , (2 , 2 ), 2 , 2 , 2 , 2 ),
1461+ (1 , (2 , 2 ), 2 , 2 , 2 , 2 , 2 ),
1462+ (1 , (2 , 2 ), 2 , 2 , 2 , 2 , 2 , 2 ),
1463+ ]
1464+
1465+ for args in invalid_args :
1466+ with self .assertRaises (TypeError ):
1467+ l .rotate (* args )
1468+
1469+ def test_meth_rotate_invalid_argtype (self ):
1470+ """Ensures that the rotate method correctly deals with invalid argument types."""
1471+ l = Line (0 , 0 , 1 , 1 )
1472+
1473+ invalid_args = [
1474+ ("a" ,), # angle str
1475+ (None ,), # angle str
1476+ ((1 , 2 )), # angle tuple
1477+ ([1 , 2 ]), # angle list
1478+ (1 , "a" ), # origin str
1479+ (1 , None ), # origin None
1480+ (1 , True ), # origin True
1481+ (1 , False ), # origin False
1482+ (1 , (1 , 2 , 3 )), # origin tuple
1483+ (1 , [1 , 2 , 3 ]), # origin list
1484+ (1 , (1 , "a" )), # origin str
1485+ (1 , ("a" , 1 )), # origin str
1486+ (1 , (1 , None )), # origin None
1487+ (1 , (None , 1 )), # origin None
1488+ (1 , (1 , (1 , 2 ))), # origin tuple
1489+ (1 , (1 , [1 , 2 ])), # origin list
1490+ ]
1491+
1492+ for value in invalid_args :
1493+ with self .assertRaises (TypeError ):
1494+ l .rotate (* value )
1495+
1496+ def test_meth_rotate_return (self ):
1497+ """Ensures that the rotate method always returns a Line."""
1498+ l = Line (0 , 0 , 1 , 1 )
1499+
1500+ for angle in float_range (- 360 , 360 , 1 ):
1501+ self .assertIsInstance (l .rotate (angle ), Line )
1502+
1503+ def test_meth_rotate (self ):
1504+ """Ensures the Line.rotate() method rotates the line correctly."""
1505+
1506+ def rotate_line (line : Line , angle , center ):
1507+ def rotate_point (x , y , rang , cx , cy ):
1508+ x -= cx
1509+ y -= cy
1510+ x_new = x * math .cos (rang ) - y * math .sin (rang )
1511+ y_new = x * math .sin (rang ) + y * math .cos (rang )
1512+ return x_new + cx , y_new + cy
1513+
1514+ angle = math .radians (angle )
1515+ x1 , y1 = line .a
1516+ x2 , y2 = line .b
1517+ cx , cy = center if center is not None else line .center
1518+ x1 , y1 = rotate_point (x1 , y1 , angle , cx , cy )
1519+ x2 , y2 = rotate_point (x2 , y2 , angle , cx , cy )
1520+ return Line (x1 , y1 , x2 , y2 )
1521+
1522+ def assert_approx_equal (line1 , line2 , eps = 1e-12 ):
1523+ self .assertAlmostEqual (line1 .x1 , line2 .x1 , delta = eps )
1524+ self .assertAlmostEqual (line1 .y1 , line2 .y1 , delta = eps )
1525+ self .assertAlmostEqual (line1 .x2 , line2 .x2 , delta = eps )
1526+ self .assertAlmostEqual (line1 .y2 , line2 .y2 , delta = eps )
1527+
1528+ l = Line (0 , 0 , 1 , 1 )
1529+ angles = float_range (- 360 , 360 , 0.5 )
1530+ centers = [(a , b ) for a in range (- 10 , 10 ) for b in range (- 10 , 10 )]
1531+ for angle in angles :
1532+ assert_approx_equal (l .rotate (angle ), rotate_line (l , angle , None ))
1533+ for center in centers :
1534+ assert_approx_equal (
1535+ l .rotate (angle , center ), rotate_line (l , angle , center )
1536+ )
1537+
1538+ def test_meth_rotate_ip (self ):
1539+ """Ensures the Line.rotate_ip() method rotates the line correctly."""
1540+
1541+ def rotate_line (line : Line , angle , center ):
1542+ def rotate_point (x , y , rang , cx , cy ):
1543+ x -= cx
1544+ y -= cy
1545+ x_new = x * math .cos (rang ) - y * math .sin (rang )
1546+ y_new = x * math .sin (rang ) + y * math .cos (rang )
1547+ return x_new + cx , y_new + cy
1548+
1549+ angle = math .radians (angle )
1550+ x1 , y1 = line .a
1551+ x2 , y2 = line .b
1552+ cx , cy = center if center is not None else line .center
1553+ x1 , y1 = rotate_point (x1 , y1 , angle , cx , cy )
1554+ x2 , y2 = rotate_point (x2 , y2 , angle , cx , cy )
1555+ return Line (x1 , y1 , x2 , y2 )
1556+
1557+ def assert_approx_equal (line1 , line2 , eps = 1e-12 ):
1558+ self .assertAlmostEqual (line1 .x1 , line2 .x1 , delta = eps )
1559+ self .assertAlmostEqual (line1 .y1 , line2 .y1 , delta = eps )
1560+ self .assertAlmostEqual (line1 .x2 , line2 .x2 , delta = eps )
1561+ self .assertAlmostEqual (line1 .y2 , line2 .y2 , delta = eps )
1562+
1563+ l = Line (0 , 0 , 1 , 1 )
1564+ angles = float_range (- 360 , 360 , 0.5 )
1565+ centers = [(a , b ) for a in range (- 10 , 10 ) for b in range (- 10 , 10 )]
1566+ for angle in angles :
1567+ new_l = l .copy ()
1568+ new_l .rotate_ip (angle )
1569+ assert_approx_equal (new_l , rotate_line (l , angle , None ))
1570+ for center in centers :
1571+ new_l = l .copy ()
1572+ new_l .rotate_ip (angle , center )
1573+ assert_approx_equal (new_l , rotate_line (l , angle , center ))
1574+
13841575
13851576if __name__ == "__main__" :
13861577 unittest .main ()
0 commit comments