LayerPolygon.py 11 KB

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