LayerPolygon.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import numpy
  4. from typing import Optional, cast
  5. from UM.Qt.Bindings.Theme import Theme
  6. from UM.Qt.QtApplication import QtApplication
  7. from UM.Logger import Logger
  8. class LayerPolygon:
  9. NoneType = 0
  10. Inset0Type = 1
  11. InsetXType = 2
  12. SkinType = 3
  13. SupportType = 4
  14. SkirtType = 5
  15. InfillType = 6
  16. SupportInfillType = 7
  17. MoveCombingType = 8
  18. MoveRetractionType = 9
  19. SupportInterfaceType = 10
  20. PrimeTowerType = 11
  21. __number_of_types = 12
  22. __jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
  23. def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
  24. """LayerPolygon, used in ProcessSlicedLayersJob
  25. :param extruder: The position of the extruder
  26. :param line_types: array with line_types
  27. :param data: new_points
  28. :param line_widths: array with line widths
  29. :param line_thicknesses: array with type as index and thickness as value
  30. :param line_feedrates: array with line feedrates
  31. """
  32. self._extruder = extruder
  33. self._types = line_types
  34. for i in range(len(self._types)):
  35. if self._types[i] >= self.__number_of_types: # Got faulty line data from the engine.
  36. Logger.log("w", "Found an unknown line type: %s", i)
  37. self._types[i] = self.NoneType
  38. self._data = data
  39. self._line_widths = line_widths
  40. self._line_thicknesses = line_thicknesses
  41. self._line_feedrates = line_feedrates
  42. self._vertex_begin = 0
  43. self._vertex_end = 0
  44. self._index_begin = 0
  45. self._index_end = 0
  46. self._jump_mask = self.__jump_map[self._types]
  47. self._jump_count = numpy.sum(self._jump_mask)
  48. self._mesh_line_count = len(self._types) - self._jump_count
  49. self._vertex_count = self._mesh_line_count + numpy.sum(self._types[1:] == self._types[:-1])
  50. # Buffering the colors shouldn't be necessary as it is not
  51. # re-used and can save alot of memory usage.
  52. self._color_map = LayerPolygon.getColorMap()
  53. self._colors = self._color_map[self._types] # type: numpy.ndarray
  54. # When type is used as index returns true if type == LayerPolygon.InfillType or type == LayerPolygon.SkinType or type == LayerPolygon.SupportInfillType
  55. # Should be generated in better way, not hardcoded.
  56. self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype = numpy.bool)
  57. self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray]
  58. self._build_cache_needed_points = None # type: Optional[numpy.ndarray]
  59. def buildCache(self) -> None:
  60. # For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out.
  61. self._build_cache_line_mesh_mask = numpy.ones(self._jump_mask.shape, dtype = bool)
  62. mesh_line_count = numpy.sum(self._build_cache_line_mesh_mask)
  63. self._index_begin = 0
  64. self._index_end = mesh_line_count
  65. self._build_cache_needed_points = numpy.ones((len(self._types), 2), dtype = numpy.bool)
  66. # Only if the type of line segment changes do we need to add an extra vertex to change colors
  67. self._build_cache_needed_points[1:, 0][:, numpy.newaxis] = self._types[1:] != self._types[:-1]
  68. # Mark points as unneeded if they are of types we don't want in the line mesh according to the calculated mask
  69. numpy.logical_and(self._build_cache_needed_points, self._build_cache_line_mesh_mask, self._build_cache_needed_points )
  70. self._vertex_begin = 0
  71. self._vertex_end = numpy.sum( self._build_cache_needed_points )
  72. def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray, colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray, extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None:
  73. """Set all the arrays provided by the function caller, representing the LayerPolygon
  74. The arrays are either by vertex or by indices.
  75. :param vertex_offset: determines where to start and end filling the arrays
  76. :param index_offset: determines where to start and end filling the arrays
  77. :param vertices: vertex numpy array to be filled
  78. :param colors: vertex numpy array to be filled
  79. :param line_dimensions: vertex numpy array to be filled
  80. :param feedrates: vertex numpy array to be filled
  81. :param extruders: vertex numpy array to be filled
  82. :param line_types: vertex numpy array to be filled
  83. :param indices: index numpy array to be filled
  84. """
  85. if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None:
  86. self.buildCache()
  87. if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None:
  88. Logger.log("w", "Failed to build cache for layer polygon")
  89. return
  90. line_mesh_mask = self._build_cache_line_mesh_mask
  91. needed_points_list = self._build_cache_needed_points
  92. # Index to the points we need to represent the line mesh. This is constructed by generating simple
  93. # start and end points for each line. For line segment n these are points n and n+1. Row n reads [n n+1]
  94. # Then then the indices for the points we don't need are thrown away based on the pre-calculated list.
  95. index_list = ( numpy.arange(len(self._types)).reshape((-1, 1)) + numpy.array([[0, 1]]) ).reshape((-1, 1))[needed_points_list.reshape((-1, 1))]
  96. # The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
  97. self._vertex_begin += vertex_offset
  98. self._vertex_end += vertex_offset
  99. # Points are picked based on the index list to get the vertices needed.
  100. vertices[self._vertex_begin:self._vertex_end, :] = self._data[index_list, :]
  101. # Create an array with colors for each vertex and remove the color data for the points that has been thrown away.
  102. colors[self._vertex_begin:self._vertex_end, :] = numpy.tile(self._colors, (1, 2)).reshape((-1, 4))[needed_points_list.ravel()]
  103. # Create an array with line widths and thicknesses for each vertex.
  104. line_dimensions[self._vertex_begin:self._vertex_end, 0] = numpy.tile(self._line_widths, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
  105. line_dimensions[self._vertex_begin:self._vertex_end, 1] = numpy.tile(self._line_thicknesses, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
  106. # Create an array with feedrates for each line
  107. feedrates[self._vertex_begin:self._vertex_end] = numpy.tile(self._line_feedrates, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
  108. extruders[self._vertex_begin:self._vertex_end] = self._extruder
  109. # Convert type per vertex to type per line
  110. line_types[self._vertex_begin:self._vertex_end] = numpy.tile(self._types, (1, 2)).reshape((-1, 1))[needed_points_list.ravel()][:, 0]
  111. # The relative values of begin and end indices have already been set in buildCache, so we only need to offset them to the parents offset.
  112. self._index_begin += index_offset
  113. self._index_end += index_offset
  114. indices[self._index_begin:self._index_end, :] = numpy.arange(self._index_end-self._index_begin, dtype = numpy.int32).reshape((-1, 1))
  115. # When the line type changes the index needs to be increased by 2.
  116. indices[self._index_begin:self._index_end, :] += numpy.cumsum(needed_points_list[line_mesh_mask.ravel(), 0], dtype = numpy.int32).reshape((-1, 1))
  117. # Each line segment goes from it's starting point p to p+1, offset by the vertex index.
  118. # The -1 is to compensate for the neccecarily True value of needed_points_list[0,0] which causes an unwanted +1 in cumsum above.
  119. indices[self._index_begin:self._index_end, :] += numpy.array([self._vertex_begin - 1, self._vertex_begin])
  120. self._build_cache_line_mesh_mask = None
  121. self._build_cache_needed_points = None
  122. def getColors(self):
  123. return self._colors
  124. def mapLineTypeToColor(self, line_types: numpy.ndarray) -> numpy.ndarray:
  125. return self._color_map[line_types]
  126. def isInfillOrSkinType(self, line_types: numpy.ndarray) -> numpy.ndarray:
  127. return self._is_infill_or_skin_type_map[line_types]
  128. def lineMeshVertexCount(self) -> int:
  129. return self._vertex_end - self._vertex_begin
  130. def lineMeshElementCount(self) -> int:
  131. return self._index_end - self._index_begin
  132. @property
  133. def extruder(self):
  134. return self._extruder
  135. @property
  136. def types(self):
  137. return self._types
  138. @property
  139. def data(self):
  140. return self._data
  141. @property
  142. def elementCount(self):
  143. return (self._index_end - self._index_begin) * 2 # The range of vertices multiplied by 2 since each vertex is used twice
  144. @property
  145. def lineWidths(self):
  146. return self._line_widths
  147. @property
  148. def lineThicknesses(self):
  149. return self._line_thicknesses
  150. @property
  151. def lineFeedrates(self):
  152. return self._line_feedrates
  153. @property
  154. def jumpMask(self):
  155. return self._jump_mask
  156. @property
  157. def meshLineCount(self):
  158. return self._mesh_line_count
  159. @property
  160. def jumpCount(self):
  161. return self._jump_count
  162. def getNormals(self) -> numpy.ndarray:
  163. """Calculate normals for the entire polygon using numpy.
  164. :return: normals for the entire polygon
  165. """
  166. normals = numpy.copy(self._data)
  167. normals[:, 1] = 0.0 # We are only interested in 2D normals
  168. # Calculate the edges between points.
  169. # The call to numpy.roll shifts the entire array by one so that
  170. # we end up subtracting each next point from the current, wrapping
  171. # around. This gives us the edges from the next point to the current
  172. # point.
  173. normals = numpy.diff(normals, 1, 0)
  174. # Calculate the length of each edge using standard Pythagoras
  175. lengths = numpy.sqrt(normals[:, 0] ** 2 + normals[:, 2] ** 2)
  176. # The normal of a 2D vector is equal to its x and y coordinates swapped
  177. # and then x inverted. This code does that.
  178. normals[:, [0, 2]] = normals[:, [2, 0]]
  179. normals[:, 0] *= -1
  180. # Normalize the normals.
  181. normals[:, 0] /= lengths
  182. normals[:, 2] /= lengths
  183. return normals
  184. __color_map = None # type: numpy.ndarray
  185. @classmethod
  186. def getColorMap(cls) -> numpy.ndarray:
  187. """Gets the instance of the VersionUpgradeManager, or creates one."""
  188. if cls.__color_map is None:
  189. theme = cast(Theme, QtApplication.getInstance().getTheme())
  190. cls.__color_map = numpy.array([
  191. theme.getColor("layerview_none").getRgbF(), # NoneType
  192. theme.getColor("layerview_inset_0").getRgbF(), # Inset0Type
  193. theme.getColor("layerview_inset_x").getRgbF(), # InsetXType
  194. theme.getColor("layerview_skin").getRgbF(), # SkinType
  195. theme.getColor("layerview_support").getRgbF(), # SupportType
  196. theme.getColor("layerview_skirt").getRgbF(), # SkirtType
  197. theme.getColor("layerview_infill").getRgbF(), # InfillType
  198. theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
  199. theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
  200. theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
  201. theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
  202. theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
  203. ])
  204. return cls.__color_map