eulerspiral.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100
  1. # Created: 26.03.2010
  2. # License: MIT License
  3. from typing import Dict, Iterable
  4. from ezdxf.math import Vector
  5. from ezdxf.math.bspline import bspline_control_frame, BSpline
  6. class EulerSpiral:
  7. """
  8. This object represents an euler spiral (clothoid) for *curvature* (Radius of curvature).
  9. This is a parametric curve, which always starts at the origin = (0, 0).
  10. """
  11. def __init__(self, curvature: float = 1.0):
  12. self.curvature = curvature # Radius of curvature
  13. self.curvature_powers = [curvature ** power for power in range(19)]
  14. self._cache = {} # type: Dict[float, Vector] # coordinates cache
  15. def radius(self, t: float) -> float:
  16. """
  17. Get radius of circle at distance 't'.
  18. """
  19. if t > 0.:
  20. return self.curvature_powers[2] / t
  21. else:
  22. return 0. # radius = infinite
  23. def tangent(self, t: float) -> Vector:
  24. """
  25. Get tangent at distance `t` as Vector() object.
  26. """
  27. angle = t ** 2 / (2. * self.curvature_powers[2])
  28. return Vector.from_angle(angle)
  29. def distance(self, radius: float) -> float:
  30. """
  31. Get distance L from origin for radius.
  32. """
  33. return self.curvature_powers[2] / float(radius)
  34. def point(self, t: float) -> Vector:
  35. """
  36. Get point at distance `t` as Vector().
  37. """
  38. def term(length_power, curvature_power, const):
  39. return t ** length_power / (const * self.curvature_powers[curvature_power])
  40. if t not in self._cache:
  41. y = term(3, 2, 6.) - term(7, 6, 336.) + term(11, 10, 42240.) - \
  42. term(15, 14, 9676800.) + term(19, 18, 3530096640.)
  43. x = t - term(5, 4, 40.) + term(9, 8, 3456.) - term(13, 12, 599040.) + \
  44. term(17, 16, 175472640.)
  45. self._cache[t] = Vector(x, y)
  46. return self._cache[t]
  47. def approximate(self, length: float, segments: int) -> Iterable[Vector]:
  48. """
  49. Approximate curve of length with line segments.
  50. Generates segments+1 vertices as Vector() objects.
  51. """
  52. delta_l = float(length) / float(segments)
  53. yield Vector(0, 0)
  54. for index in range(1, segments + 1):
  55. yield self.point(delta_l * index)
  56. def circle_midpoint(self, t: float) -> Vector:
  57. """
  58. Get circle midpoint at distance `t`.
  59. """
  60. p = self.point(t)
  61. r = self.radius(t)
  62. return p + self.tangent(t).normalize(r).orthogonal()
  63. def bspline(self, length: float, segments: int = 10, degree: int = 3, method: str = 'uniform') -> BSpline:
  64. """
  65. Approximate euler spiral by B-spline.
  66. Args:
  67. length: length of euler spiral
  68. segments: count of fit points for B-spline calculation
  69. degree: degree of BSpline
  70. method: calculation method for parameter vector t
  71. """
  72. fit_points = list(self.approximate(length, segments=segments))
  73. spline = bspline_control_frame(fit_points, degree, method=method)
  74. knots = [v * length for v in spline.knot_values()] # scale knot values to length
  75. spline.basis.knots = knots
  76. return spline