Coverage for src \ pavpen \ geometry_calculator \ circle_calculator.py: 100%

16 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-27 03:59 -0400

1# SPDX-FileCopyrightText: 2026-present Pavel M. Penev <pavpen@gmail.com> 

2# 

3# SPDX-License-Identifier: MIT 

4 

5import logging 

6import math 

7 

8from pavpen.geometry_calculator.float_vector_field_operations import FloatVectorFieldOperations 

9 

10logger = logging.getLogger(__name__) 

11 

12 

13class CircleCalculator[Vector]: 

14 """Calculates the location of points on a circle (or an ellipse, if the 

15 norms of :emphasis:`x_hat`, and :emphasis:`y_hat` are different) 

16 

17 Example: 

18 

19 >>> import math 

20 >>> from pavpen.geometry_calculator import CircleCalculator 

21 >>> from pavpen.geometry_calculator.vector_field_implementations import ( 

22 ... TupleFloatVectorFieldOperations, 

23 ... ) 

24 >>> 

25 >>> (x, y) = CircleCalculator( 

26 ... vector_field_operations=TupleFloatVectorFieldOperations.for_2d(), 

27 ... center=(0, 0), 

28 ... radius=1, 

29 ... x_hat=(1, 0), 

30 ... y_hat=(0, 1), 

31 ... ).point_at_angle_rad(math.pi / 4) 

32 >>> print(f"({x:.4f}, {y:.4f})") 

33 (0.7071, 0.7071) 

34 """ 

35 

36 def __init__( 

37 self, 

38 vector_field_operations: FloatVectorFieldOperations[Vector], 

39 center: Vector, 

40 radius: float, 

41 x_hat: Vector | None = None, 

42 y_hat: Vector | None = None, 

43 ): 

44 self.vector_field_operations = vector_field_operations 

45 self.center = center 

46 self.radius = radius 

47 self._x_hat = vector_field_operations.additive_identity if x_hat is None else x_hat 

48 self._y_hat = vector_field_operations.additive_identity if y_hat is None else y_hat 

49 

50 def point_at_angle_rad(self, angle_rad: float) -> Vector: 

51 """Location of the point at angle *angle_rad* in the right 

52 direction from ``x_hat`` the on this circle 

53 

54 The value of the ``x_hat`` argument is defined when calling the 

55 :py:meth:`constructor <CircleCalculator>`. 

56 """ 

57 

58 vec = self.vector_field_operations 

59 x_offset = vec.scaled(self._x_hat, self.radius * math.cos(angle_rad)) 

60 y_offset = vec.scaled(self._y_hat, self.radius * math.sin(angle_rad)) 

61 

62 return vec.summed(self.center, x_offset, y_offset)