construct2d.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. # Copyright (c) 2010-2018 Manfred Moitzi
  2. # License: MIT License
  3. from typing import TYPE_CHECKING
  4. from functools import partial
  5. import math
  6. from operator import le, ge, lt, gt
  7. from abc import abstractmethod
  8. if TYPE_CHECKING:
  9. from ezdxf.eztypes import BoundingBox2d, Vertex
  10. HALF_PI = math.pi / 2. # type: float
  11. THREE_PI_HALF = 1.5 * math.pi # type: float
  12. DOUBLE_PI = math.pi * 2. # type: float
  13. def is_close_points(p1: 'Vertex', p2: 'Vertex', abs_tol=1e-12) -> bool:
  14. """
  15. Returns true if `p1` is very close to `p2`.
  16. Args:
  17. p1: vertex 1
  18. p2: vertex 2
  19. abs_tol: absolute tolerance
  20. """
  21. if len(p1) != len(p2):
  22. raise TypeError('incompatible points')
  23. for v1, v2 in zip(p1, p2):
  24. if not math.isclose(v1, v2, abs_tol=abs_tol):
  25. return False
  26. return True
  27. def normalize_angle(angle: float) -> float:
  28. """
  29. Returns normalized angle between 0 and 2*pi.
  30. """
  31. angle = math.fmod(angle, DOUBLE_PI)
  32. if angle < 0:
  33. angle += DOUBLE_PI
  34. return angle
  35. def enclosing_angles(angle, start_angle, end_angle, ccw=True, abs_tol=1e-9):
  36. isclose = partial(math.isclose, abs_tol=abs_tol)
  37. s = normalize_angle(start_angle)
  38. e = normalize_angle(end_angle)
  39. a = normalize_angle(angle)
  40. if isclose(s, e):
  41. return isclose(s, a)
  42. if s < e:
  43. r = s < a < e
  44. else:
  45. r = not (e < a < s)
  46. return r if ccw else not r
  47. def left_of_line(point: 'Vertex', p1: 'Vertex', p2: 'Vertex', online=False) -> bool:
  48. """
  49. True if `point` is "left of line" (`p1`, `p2`). Point on the line is "left of line" if `online` is True.
  50. """
  51. px, py, *_ = point
  52. p1x, p1y, *_ = p1
  53. p2x, p2y, *_ = p2
  54. if online:
  55. lower, greater = le, ge # lower/greater or equal
  56. else:
  57. lower, greater = lt, gt # real lower/greater then
  58. # check if p1 and p2 are on the same vertical line
  59. if math.isclose(p1x, p2x):
  60. # compute on which side of the line point should be
  61. should_be_left = p1y < p2y
  62. return lower(px, p1x) if should_be_left else greater(px, p1y)
  63. else:
  64. # get pitch of line
  65. pitch = (p2y - p1y) / (p2x - p1x)
  66. # get y-value at points's x-position
  67. y = pitch * (px - p1x) + p1y
  68. # compute if point should be above or below the line
  69. should_be_above = p1x < p2x
  70. return greater(py, y) if should_be_above else lower(py, y)
  71. class ConstructionTool:
  72. """
  73. Abstract base class for all 2D construction classes.
  74. """
  75. @property
  76. @abstractmethod
  77. def bounding_box(self) -> 'BoundingBox2d':
  78. pass
  79. @abstractmethod
  80. def move(self, dx: float, dy: float) -> None:
  81. pass