utils.py 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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_direct_around_positions_of_position(position, exclude_start_position=True) -> list:
  145. """
  146. TODO: compute with z (allow or disable with parameter)
  147. Return positions around a point with distance of 1 on left/right top/bottom only.
  148. :param position: (x, y, z) tuple
  149. :param exclude_start_position: if True, given position will not be
  150. added to result list
  151. :return: list of (x, y, z) positions
  152. :rtype: list
  153. """
  154. pz = position[2]
  155. px = position[0]
  156. py = position[1]
  157. points = [
  158. (px, py-1, pz),
  159. (px-1, py , pz),
  160. (px+1, py , pz),
  161. (px, py+1, pz),
  162. ]
  163. if not exclude_start_position:
  164. points.append(position)
  165. return points
  166. def get_around_positions_of(
  167. position,
  168. distance=1,
  169. exclude_start_point=True,
  170. ) -> list:
  171. """
  172. Return positions around a point.
  173. :param position: (x, y, z) tuple
  174. :param distance: Distance to compute
  175. :return: list of (x, y, z) positions
  176. """
  177. start_x = position[0] - distance
  178. start_y = position[1] - distance
  179. # start_z = position[0] - distance
  180. positions = []
  181. range_distance = (distance * 2) + 1
  182. for dx in range(range_distance):
  183. for dy in range(range_distance):
  184. # for dz in range(range_distance):
  185. # points.append((start_z+dz, start_x+dx, start_y+dy))
  186. positions.append((start_x + dx, start_y + dy, position[2]))
  187. if exclude_start_point:
  188. positions.remove(position)
  189. return positions
  190. def get_distance_between_points(a: tuple, b: tuple) -> float:
  191. return abs(sqrt((b[0] - a[0]) ** 2 + (b[1] - a[1]) ** 2))
  192. def get_position_for_direction(from_position: tuple, direction: int) -> tuple:
  193. modifier = DIRECTION_MODIFIERS[direction]
  194. return (
  195. from_position[0] + modifier[0],
  196. from_position[1] + modifier[1],
  197. from_position[2] + modifier[2],
  198. )
  199. def get_angle(a: typing.Tuple[int, int], b: typing.Tuple[int, int]) -> int:
  200. b = (b[0] - a[0], b[1] - a[1])
  201. a = 0, 1
  202. ang1 = numpy.arctan2(*a[::-1])
  203. ang2 = numpy.arctan2(*b[::-1])
  204. return numpy.rad2deg((ang1 - ang2) % (2 * numpy.pi))
  205. def get_line_xy_path(start, end):
  206. """
  207. TODO: copied from http://www.roguebasin.com/index.php?title=Bresenham%27s_Line_Algorithm#Python
  208. What is the licence ?
  209. Bresenham's Line Algorithm
  210. Produces a list of tuples from start and end
  211. >>> points1 = get_line((0, 0), (3, 4))
  212. >>> points2 = get_line((3, 4), (0, 0))
  213. >>> assert(set(points1) == set(points2))
  214. >>> print points1
  215. [(0, 0), (1, 1), (1, 2), (2, 3), (3, 4)]
  216. >>> print points2
  217. [(3, 4), (2, 3), (1, 2), (1, 1), (0, 0)]
  218. """
  219. # Setup initial conditions
  220. x1, y1 = start
  221. x2, y2 = end
  222. dx = x2 - x1
  223. dy = y2 - y1
  224. # Determine how steep the line is
  225. is_steep = abs(dy) > abs(dx)
  226. # Rotate line
  227. if is_steep:
  228. x1, y1 = y1, x1
  229. x2, y2 = y2, x2
  230. # Swap start and end points if necessary and store swap state
  231. swapped = False
  232. if x1 > x2:
  233. x1, x2 = x2, x1
  234. y1, y2 = y2, y1
  235. swapped = True
  236. # Recalculate differentials
  237. dx = x2 - x1
  238. dy = y2 - y1
  239. # Calculate error
  240. error = int(dx / 2.0)
  241. ystep = 1 if y1 < y2 else -1
  242. # Iterate over bounding box generating points between start and end
  243. y = y1
  244. points = []
  245. for x in range(x1, x2 + 1):
  246. coord = (y, x) if is_steep else (x, y)
  247. points.append(coord)
  248. error -= abs(dy)
  249. if error < 0:
  250. y += ystep
  251. error += dx
  252. # Reverse the list if the coordinates were swapped
  253. if swapped:
  254. points.reverse()
  255. return points