ucs.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. # Copyright (c) 2018 Manfred Moitzi
  2. # License: MIT License
  3. from typing import TYPE_CHECKING, Tuple, Sequence, Iterable
  4. from .vector import Vector, X_AXIS, Y_AXIS, Z_AXIS
  5. if TYPE_CHECKING:
  6. from ezdxf.eztypes import GenericLayoutType, Vertex
  7. def render_axis(layout: 'GenericLayoutType',
  8. start: 'Vertex',
  9. points: Sequence['Vertex'],
  10. colors: Tuple[int, int, int] = (1, 3, 5)) -> None:
  11. for point, color in zip(points, colors):
  12. layout.add_line(start, point, dxfattribs={'color': color})
  13. class Matrix33:
  14. """
  15. Simple 3x3 Matrix for coordinate transformation.
  16. """
  17. __slots__ = ('ux', 'uy', 'uz')
  18. def __init__(self, ux: 'Vertex' = (1, 0, 0), uy: 'Vertex' = (0, 1, 0), uz: 'Vertex' = (0, 0, 1)):
  19. self.ux = Vector(ux)
  20. self.uy = Vector(uy)
  21. self.uz = Vector(uz)
  22. def transpose(self) -> 'Matrix33':
  23. return Matrix33(
  24. (self.ux.x, self.uy.x, self.uz.x),
  25. (self.ux.y, self.uy.y, self.uz.y),
  26. (self.ux.z, self.uy.z, self.uz.z),
  27. )
  28. def transform(self, vector: 'Vertex') -> Vector:
  29. px, py, pz = Vector(vector)
  30. ux = self.ux
  31. uy = self.uy
  32. uz = self.uz
  33. x = px * ux.x + py * uy.x + pz * uz.x
  34. y = px * ux.y + py * uy.y + pz * uz.y
  35. z = px * ux.z + py * uy.z + pz * uz.z
  36. return Vector(x, y, z)
  37. class OCS:
  38. def __init__(self, extrusion: 'Vertex' = Z_AXIS):
  39. Az = Vector(extrusion).normalize()
  40. self.transform = not Az.isclose(Z_AXIS)
  41. if self.transform:
  42. if (abs(Az.x) < 1 / 64.) and (abs(Az.y) < 1 / 64.):
  43. Ax = Y_AXIS.cross(Az)
  44. else:
  45. Ax = Z_AXIS.cross(Az)
  46. Ax = Ax.normalize()
  47. Ay = Az.cross(Ax).normalize()
  48. self.matrix = Matrix33(Ax, Ay, Az)
  49. self.transpose = self.matrix.transpose()
  50. @property
  51. def ux(self) -> Vector:
  52. return self.matrix.ux if self.transform else X_AXIS
  53. @property
  54. def uy(self) -> Vector:
  55. return self.matrix.uy if self.transform else Y_AXIS
  56. @property
  57. def uz(self) -> Vector:
  58. return self.matrix.uz if self.transform else Z_AXIS
  59. def from_wcs(self, point: 'Vertex') -> 'Vertex':
  60. if self.transform:
  61. return self.transpose.transform(point)
  62. else:
  63. return point
  64. def points_from_wcs(self, points: Iterable['Vertex']) -> Iterable['Vertex']:
  65. for point in points:
  66. yield self.from_wcs(point)
  67. def to_wcs(self, point: 'Vertex') -> 'Vertex':
  68. if self.transform:
  69. return self.matrix.transform(point)
  70. else:
  71. return point
  72. def points_to_wcs(self, points: Iterable['Vertex']) -> Iterable['Vertex']:
  73. for point in points:
  74. yield self.to_wcs(point)
  75. def render_axis(self, layout: 'GenericLayoutType', length: float = 1, colors: Tuple[int, int, int] = (1, 3, 5)):
  76. render_axis(
  77. layout,
  78. start=(0, 0, 0),
  79. points=(
  80. self.to_wcs(X_AXIS * length),
  81. self.to_wcs(Y_AXIS * length),
  82. self.to_wcs(Z_AXIS * length),
  83. ),
  84. colors=colors,
  85. )
  86. class UCS:
  87. def __init__(self, origin: 'Vertex' = (0, 0, 0), ux: 'Vertex' = None, uy: 'Vertex' = None, uz: 'Vertex' = None):
  88. self.origin = Vector(origin)
  89. if ux is None and uy is None:
  90. ux = X_AXIS
  91. uy = Y_AXIS
  92. uz = Z_AXIS
  93. elif ux is None:
  94. uy = Vector(uy).normalize()
  95. uz = Vector(uz).normalize()
  96. ux = Vector(uy).cross(uz).normalize()
  97. elif uy is None:
  98. ux = Vector(ux).normalize()
  99. uz = Vector(uz).normalize()
  100. uy = Vector(uz).cross(ux).normalize()
  101. elif uz is None:
  102. ux = Vector(ux).normalize()
  103. uy = Vector(uy).normalize()
  104. uz = Vector(ux).cross(uy).normalize()
  105. else: # all axis are given
  106. ux = Vector(ux).normalize()
  107. uy = Vector(uy).normalize()
  108. uz = Vector(uz).normalize()
  109. self.matrix = Matrix33(ux, uy, uz)
  110. self.transpose = self.matrix.transpose()
  111. @property
  112. def ux(self) -> Vector:
  113. return self.matrix.ux
  114. @property
  115. def uy(self) -> Vector:
  116. return self.matrix.uy
  117. @property
  118. def uz(self) -> Vector:
  119. return self.matrix.uz
  120. def to_wcs(self, point: 'Vertex') -> Vector:
  121. """
  122. Calculate world coordinates for point in UCS coordinates.
  123. """
  124. return self.origin + self.matrix.transform(point)
  125. def points_to_wcs(self, points: Iterable['Vertex']) -> Iterable[Vector]:
  126. """
  127. Translate multiple user coordinates into world coordinates (generator).
  128. """
  129. for point in points:
  130. yield self.to_wcs(point)
  131. def to_ocs(self, point: 'Vertex') -> 'Vertex':
  132. """
  133. Calculate OCS coordinates for point in UCS coordinates.
  134. OCS is defined by the z-axis of the UCS.
  135. """
  136. wpoint = self.to_wcs(point)
  137. return OCS(self.uz).from_wcs(wpoint)
  138. def points_to_ocs(self, points: Iterable['Vertex']) -> Iterable['Vertex']:
  139. """
  140. Translate multiple user coordinates into OCS coordinates (generator).
  141. OCS is defined by the z-axis of the UCS.
  142. """
  143. wcs = self.to_wcs
  144. ocs = OCS(self.uz)
  145. for point in points:
  146. yield ocs.from_wcs(wcs(point))
  147. def to_ocs_angle_deg(self, angle: float) -> float:
  148. """
  149. Transform angle in UCS xy-plane to angle in OCS xy-plane.
  150. Args:
  151. angle: in UCS in degrees
  152. Returns: angle in OCS in degrees
  153. """
  154. vec = Vector.from_deg_angle(angle)
  155. vec = self.to_ocs(vec) - self.origin
  156. return vec.angle_deg
  157. def to_ocs_angle_rad(self, angle: float) -> float:
  158. """
  159. Transform angle in UCS xy-plane to angle in OCS xy-plane.
  160. Args:
  161. angle: in UCS in radians
  162. Returns: angle in OCS in radians
  163. """
  164. vec = Vector.from_angle(angle)
  165. vec = self.to_ocs(vec) - self.origin
  166. return vec.angle
  167. def from_wcs(self, point: 'Vertex') -> Vector:
  168. """
  169. Calculate UCS coordinates for point in world coordinates.
  170. """
  171. return self.transpose.transform(point - self.origin)
  172. def points_from_wcs(self, points: Iterable['Vertex']) -> Iterable[Vector]:
  173. """
  174. Translate multiple world coordinates into user coordinates (generator).
  175. """
  176. for point in points:
  177. yield self.from_wcs(point)
  178. @property
  179. def is_cartesian(self) -> bool:
  180. return self.uy.cross(self.uz).isclose(self.ux)
  181. @staticmethod
  182. def from_x_axis_and_point_in_xy(origin: 'Vertex', axis: 'Vertex', point: 'Vertex') -> 'UCS':
  183. x_axis = Vector(axis)
  184. z_axis = x_axis.cross(Vector(point) - origin)
  185. return UCS(origin=origin, ux=x_axis, uz=z_axis)
  186. @staticmethod
  187. def from_x_axis_and_point_in_xz(origin: 'Vertex', axis: 'Vertex', point: 'Vertex') -> 'UCS':
  188. x_axis = Vector(axis)
  189. xz_vector = Vector(point) - origin
  190. y_axis = xz_vector.cross(x_axis)
  191. return UCS(origin=origin, ux=x_axis, uy=y_axis)
  192. @staticmethod
  193. def from_y_axis_and_point_in_xy(origin: 'Vertex', axis: 'Vertex', point: 'Vertex') -> 'UCS':
  194. y_axis = Vector(axis)
  195. xy_vector = Vector(point) - origin
  196. z_axis = xy_vector.cross(y_axis)
  197. return UCS(origin=origin, uy=y_axis, uz=z_axis)
  198. @staticmethod
  199. def from_y_axis_and_point_in_yz(origin: 'Vertex', axis: 'Vertex', point: 'Vertex') -> 'UCS':
  200. y_axis = Vector(axis)
  201. yz_vector = Vector(point) - origin
  202. x_axis = yz_vector.cross(y_axis)
  203. return UCS(origin=origin, ux=x_axis, uy=y_axis)
  204. @staticmethod
  205. def from_z_axis_and_point_in_xz(origin: 'Vertex', axis: 'Vertex', point: 'Vertex') -> 'UCS':
  206. z_axis = Vector(axis)
  207. y_axis = z_axis.cross(Vector(point) - origin)
  208. return UCS(origin=origin, uy=y_axis, uz=z_axis)
  209. @staticmethod
  210. def from_z_axis_and_point_in_yz(origin: 'Vertex', axis: 'Vertex', point: 'Vertex') -> 'UCS':
  211. z_axis = Vector(axis)
  212. yz_vector = Vector(point) - origin
  213. x_axis = yz_vector.cross(z_axis)
  214. return UCS(origin=origin, ux=x_axis, uz=z_axis)
  215. def render_axis(self, layout: 'GenericLayoutType', length: float = 1, colors: Tuple[int, int, int] = (1, 3, 5)):
  216. render_axis(
  217. layout,
  218. start=self.origin,
  219. points=(
  220. self.to_wcs(X_AXIS * length),
  221. self.to_wcs(Y_AXIS * length),
  222. self.to_wcs(Z_AXIS * length),
  223. ),
  224. colors=colors,
  225. )
  226. class PassTroughUCS(UCS):
  227. """ UCS is equal to the WCS and OCS (extrusion = 0, 0, 1) """
  228. def __init__(self):
  229. super().__init__()
  230. def to_wcs(self, point: 'Vertex') -> Vector:
  231. return Vector(point)
  232. def points_to_wcs(self, points: Iterable['Vertex']) -> Iterable[Vector]:
  233. for point in points:
  234. yield Vector(point)
  235. def to_ocs(self, point: 'Vertex') -> 'Vertex':
  236. return Vector(point)
  237. def points_to_ocs(self, points: Iterable['Vertex']) -> Iterable['Vertex']:
  238. for point in points:
  239. yield Vector(point)
  240. def to_ocs_angle_deg(self, angle: float) -> float:
  241. return angle
  242. def to_ocs_angle_rad(self, angle: float) -> float:
  243. return angle
  244. def from_wcs(self, point: 'Vertex') -> Vector:
  245. return Vector(point)
  246. def points_from_wcs(self, points: Iterable['Vertex']) -> Iterable[Vector]:
  247. for point in points:
  248. yield Vector(point)