LayerPolygon.py 11 KB

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