utils.py 8.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. # coding: utf-8
  2. import collections
  3. import typing
  4. from math import sqrt
  5. import numpy
  6. from synergine2_xyz.xyz import DIRECTION_MODIFIERS
  7. def get_positions_from_str_representation(str_representation):
  8. # TODO: Manage z axis (like ------------ as separator)
  9. lines = str_representation.split("\n") # One item per lines
  10. lines = map(lambda l: l.strip().replace(' ', ''), lines) # Remove spaces
  11. lines = filter(lambda l: bool(l), lines) # Only line with content
  12. lines = list(lines)
  13. width = len(lines[0])
  14. height = len(lines)
  15. if not width % 2 or not height % 2:
  16. raise Exception(
  17. 'Width and height of your representation must be odd. '
  18. 'Actually it\'s {0}x{1}'.format(
  19. width,
  20. height,
  21. ))
  22. items_positions = collections.defaultdict(list)
  23. start_x = - int(width / 2 - 0.5)
  24. start_y = - int(height / 2 - 0.5)
  25. start_z = 0
  26. current_y = start_y
  27. current_z = start_z
  28. for line in lines:
  29. current_x = start_x
  30. for char in line:
  31. items_positions[char].append((
  32. current_x,
  33. current_y,
  34. current_z,
  35. ))
  36. current_x += 1
  37. current_y += 1
  38. return items_positions
  39. def get_min_and_max(positions) -> (int, int, int, int, int):
  40. max_x_position = max(positions, key=lambda p: p[0])
  41. min_x_position = min(positions, key=lambda p: p[0])
  42. max_y_position = max(positions, key=lambda p: p[1])
  43. min_y_position = min(positions, key=lambda p: p[1])
  44. max_z_position = max(positions, key=lambda p: p[2])
  45. min_z_position = min(positions, key=lambda p: p[2])
  46. max_x = max_x_position[0]
  47. min_x = min_x_position[0]
  48. max_y = max_y_position[1]
  49. min_y = min_y_position[1]
  50. max_z = max_z_position[2]
  51. min_z = min_z_position[2]
  52. return min_x, max_x, min_y, max_y, min_z, max_z
  53. def get_str_representation_from_positions(
  54. items_positions: dict,
  55. separator='',
  56. tabulation='',
  57. start_with='',
  58. end_with='',
  59. force_items_as=None,
  60. force_positions_as=None,
  61. complete_lines_with=' ',
  62. ) -> str:
  63. positions = []
  64. for item_positions in items_positions.values():
  65. positions.extend(item_positions)
  66. positions = sorted(positions, key=lambda p: (p[2], p[1], p[0]))
  67. if complete_lines_with is not None:
  68. min_x, max_x, min_y, max_y, min_z, max_z = get_min_and_max(positions)
  69. all_ = []
  70. for x in range(min_x, max_x+1):
  71. for y in range(min_y, max_y+1):
  72. for z in range(min_z, max_z+1):
  73. all_.append((x, y, z))
  74. pass
  75. for one_of_all in all_:
  76. if one_of_all not in positions:
  77. if complete_lines_with not in items_positions:
  78. items_positions[complete_lines_with] = []
  79. items_positions[complete_lines_with].append(one_of_all)
  80. positions = []
  81. for item_positions in items_positions.values():
  82. positions.extend(item_positions)
  83. positions = sorted(positions, key=lambda p: (p[2], p[1], p[0]))
  84. str_representation = start_with + tabulation
  85. start_x = positions[0][0]
  86. start_y = positions[0][1]
  87. start_z = positions[0][2]
  88. current_y = start_y
  89. current_z = start_z
  90. for position in positions:
  91. item = None
  92. for parsed_item in items_positions:
  93. if position in items_positions[parsed_item]:
  94. item = parsed_item
  95. break
  96. if position[1] != current_y:
  97. str_representation += "\n" + tabulation
  98. if position[2] != current_z:
  99. str_representation += '----' + "\n" + tabulation
  100. str_item = item
  101. if force_items_as:
  102. for force_item_as in force_items_as:
  103. if force_item_as[0] == item:
  104. str_item = force_item_as[1]
  105. break
  106. if force_positions_as:
  107. for force_position_as in force_positions_as:
  108. if position == force_position_as[0]:
  109. str_item = force_position_as[1]
  110. break
  111. added_value = str_item
  112. if position[0] != start_x:
  113. added_value = separator + added_value
  114. str_representation += added_value
  115. current_y = position[1]
  116. current_z = position[2]
  117. return str_representation + end_with
  118. def get_around_positions_of_positions(position, exclude_start_position=True) -> list:
  119. """
  120. TODO: compute with z (allow or disable with parameter)
  121. Return positions around a point with distance of 1.
  122. :param position: (x, y, z) tuple
  123. :param exclude_start_position: if True, given position will not be
  124. added to result list
  125. :return: list of (x, y, z) positions
  126. :rtype: list
  127. """
  128. pz = position[2]
  129. px = position[0]
  130. py = position[1]
  131. points = [
  132. (px-1, py-1, pz),
  133. (px, py-1, pz),
  134. (px+1, py+1, pz),
  135. (px-1, py , pz),
  136. (px+1, py , pz),
  137. (px-1, py+1, pz),
  138. (px, py+1, pz),
  139. (px+1, py-1, pz)
  140. ]
  141. if not exclude_start_position:
  142. points.append(position)
  143. return points
  144. def get_around_positions_of(
  145. position,
  146. distance=1,
  147. exclude_start_point=True,
  148. ) -> list:
  149. """
  150. Return positions around a point.
  151. :param position: (x, y, z) tuple
  152. :param distance: Distance to compute
  153. :return: list of (x, y, z) positions
  154. """
  155. start_x = position[0] - distance
  156. start_y = position[1] - distance
  157. # start_z = position[0] - distance
  158. positions = []
  159. range_distance = (distance * 2) + 1
  160. for dx in range(range_distance):
  161. for dy in range(range_distance):
  162. # for dz in range(range_distance):
  163. # points.append((start_z+dz, start_x+dx, start_y+dy))
  164. positions.append((start_x + dx, start_y + dy, position[2]))
  165. if exclude_start_point:
  166. positions.remove(position)
  167. return positions
  168. def get_distance_between_points(a: tuple, b: tuple) -> float:
  169. return abs(sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2))
  170. def get_position_for_direction(from_position: tuple, direction: int) -> tuple:
  171. modifier = DIRECTION_MODIFIERS[direction]
  172. return (
  173. from_position[0] + modifier[0],
  174. from_position[1] + modifier[1],
  175. from_position[2] + modifier[2],
  176. )
  177. def get_angle(a: typing.Tuple[int, int], b: typing.Tuple[int, int]) -> int:
  178. b = (b[0] - a[0], b[1] - a[1])
  179. a = 0, 1
  180. ang1 = numpy.arctan2(*a[::-1])
  181. ang2 = numpy.arctan2(*b[::-1])
  182. return numpy.rad2deg((ang1 - ang2) % (2 * numpy.pi))
  183. def get_line_xy_path(start, end):
  184. """
  185. TODO: copied from http://www.roguebasin.com/index.php?title=Bresenham%27s_Line_Algorithm#Python
  186. What is the licence ?
  187. Bresenham's Line Algorithm
  188. Produces a list of tuples from start and end
  189. >>> points1 = get_line((0, 0), (3, 4))
  190. >>> points2 = get_line((3, 4), (0, 0))
  191. >>> assert(set(points1) == set(points2))
  192. >>> print points1
  193. [(0, 0), (1, 1), (1, 2), (2, 3), (3, 4)]
  194. >>> print points2
  195. [(3, 4), (2, 3), (1, 2), (1, 1), (0, 0)]
  196. """
  197. # Setup initial conditions
  198. x1, y1 = start
  199. x2, y2 = end
  200. dx = x2 - x1
  201. dy = y2 - y1
  202. # Determine how steep the line is
  203. is_steep = abs(dy) > abs(dx)
  204. # Rotate line
  205. if is_steep:
  206. x1, y1 = y1, x1
  207. x2, y2 = y2, x2
  208. # Swap start and end points if necessary and store swap state
  209. swapped = False
  210. if x1 > x2:
  211. x1, x2 = x2, x1
  212. y1, y2 = y2, y1
  213. swapped = True
  214. # Recalculate differentials
  215. dx = x2 - x1
  216. dy = y2 - y1
  217. # Calculate error
  218. error = int(dx / 2.0)
  219. ystep = 1 if y1 < y2 else -1
  220. # Iterate over bounding box generating points between start and end
  221. y = y1
  222. points = []
  223. for x in range(x1, x2 + 1):
  224. coord = (y, x) if is_steep else (x, y)
  225. points.append(coord)
  226. error -= abs(dy)
  227. if error < 0:
  228. y += ystep
  229. error += dx
  230. # Reverse the list if the coordinates were swapped
  231. if swapped:
  232. points.reverse()
  233. return points