SimulationPass.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. # Copyright (c) 2020 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. from UM.Math.Color import Color
  4. from UM.Math.Vector import Vector
  5. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  6. from UM.Resources import Resources
  7. from UM.Scene.SceneNode import SceneNode
  8. from UM.Scene.ToolHandle import ToolHandle
  9. from UM.Application import Application
  10. from UM.PluginRegistry import PluginRegistry
  11. from UM.View.RenderPass import RenderPass
  12. from UM.View.RenderBatch import RenderBatch
  13. from UM.View.GL.OpenGL import OpenGL
  14. from cura.Settings.ExtruderManager import ExtruderManager
  15. from cura.LayerPolygon import LayerPolygon
  16. import os.path
  17. import numpy
  18. ## RenderPass used to display g-code paths.
  19. from .NozzleNode import NozzleNode
  20. class SimulationPass(RenderPass):
  21. def __init__(self, width, height):
  22. super().__init__("simulationview", width, height)
  23. self._layer_shader = None
  24. self._layer_shadow_shader = None
  25. self._current_shader = None # This shader will be the shadow or the normal depending if the user wants to see the paths or the layers
  26. self._tool_handle_shader = None
  27. self._nozzle_shader = None
  28. self._disabled_shader = None
  29. self._old_current_layer = 0
  30. self._old_current_path = 0
  31. self._switching_layers = True # Tracking whether the user is moving across layers (True) or across paths (False). If false, lower layers render as shadowy.
  32. self._gl = OpenGL.getInstance().getBindingsObject()
  33. self._scene = Application.getInstance().getController().getScene()
  34. self._extruder_manager = ExtruderManager.getInstance()
  35. self._layer_view = None
  36. self._compatibility_mode = None
  37. self._scene.sceneChanged.connect(self._onSceneChanged)
  38. def setSimulationView(self, layerview):
  39. self._layer_view = layerview
  40. self._compatibility_mode = layerview.getCompatibilityMode()
  41. def render(self):
  42. if not self._layer_shader:
  43. if self._compatibility_mode:
  44. shader_filename = "layers.shader"
  45. shadow_shader_filename = "layers_shadow.shader"
  46. else:
  47. shader_filename = "layers3d.shader"
  48. shadow_shader_filename = "layers3d_shadow.shader"
  49. self._layer_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shader_filename))
  50. self._layer_shadow_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("SimulationView"), shadow_shader_filename))
  51. self._current_shader = self._layer_shader
  52. # Use extruder 0 if the extruder manager reports extruder index -1 (for single extrusion printers)
  53. self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
  54. if not self._compatibility_mode:
  55. self._layer_shader.setUniformValue("u_starts_color", Color(*Application.getInstance().getTheme().getColor("layerview_starts").getRgb()))
  56. if self._layer_view:
  57. self._layer_shader.setUniformValue("u_max_feedrate", self._layer_view.getMaxFeedrate())
  58. self._layer_shader.setUniformValue("u_min_feedrate", self._layer_view.getMinFeedrate())
  59. self._layer_shader.setUniformValue("u_max_thickness", self._layer_view.getMaxThickness())
  60. self._layer_shader.setUniformValue("u_min_thickness", self._layer_view.getMinThickness())
  61. self._layer_shader.setUniformValue("u_max_line_width", self._layer_view.getMaxLineWidth())
  62. self._layer_shader.setUniformValue("u_min_line_width", self._layer_view.getMinLineWidth())
  63. self._layer_shader.setUniformValue("u_max_flow_rate", self._layer_view.getMaxFlowRate())
  64. self._layer_shader.setUniformValue("u_min_flow_rate", self._layer_view.getMinFlowRate())
  65. self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getSimulationViewType())
  66. self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
  67. self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
  68. self._layer_shader.setUniformValue("u_show_helpers", self._layer_view.getShowHelpers())
  69. self._layer_shader.setUniformValue("u_show_skin", self._layer_view.getShowSkin())
  70. self._layer_shader.setUniformValue("u_show_infill", self._layer_view.getShowInfill())
  71. self._layer_shader.setUniformValue("u_show_starts", self._layer_view.getShowStarts())
  72. else:
  73. #defaults
  74. self._layer_shader.setUniformValue("u_max_feedrate", 1)
  75. self._layer_shader.setUniformValue("u_min_feedrate", 0)
  76. self._layer_shader.setUniformValue("u_max_thickness", 1)
  77. self._layer_shader.setUniformValue("u_min_thickness", 0)
  78. self._layer_shader.setUniformValue("u_max_flow_rate", 1)
  79. self._layer_shader.setUniformValue("u_min_flow_rate", 0)
  80. self._layer_shader.setUniformValue("u_max_line_width", 1)
  81. self._layer_shader.setUniformValue("u_min_line_width", 0)
  82. self._layer_shader.setUniformValue("u_layer_view_type", 1)
  83. self._layer_shader.setUniformValue("u_extruder_opacity", [[1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1], [1, 1, 1, 1]])
  84. self._layer_shader.setUniformValue("u_show_travel_moves", 0)
  85. self._layer_shader.setUniformValue("u_show_helpers", 1)
  86. self._layer_shader.setUniformValue("u_show_skin", 1)
  87. self._layer_shader.setUniformValue("u_show_infill", 1)
  88. self._layer_shader.setUniformValue("u_show_starts", 1)
  89. if not self._tool_handle_shader:
  90. self._tool_handle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "toolhandle.shader"))
  91. if not self._nozzle_shader:
  92. self._nozzle_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
  93. self._nozzle_shader.setUniformValue("u_color", Color(*Application.getInstance().getTheme().getColor("layerview_nozzle").getRgb()))
  94. if not self._disabled_shader:
  95. self._disabled_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
  96. self._disabled_shader.setUniformValue("u_diffuseColor1", Color(*Application.getInstance().getTheme().getColor("model_unslicable").getRgb()))
  97. self._disabled_shader.setUniformValue("u_diffuseColor2", Color(*Application.getInstance().getTheme().getColor("model_unslicable_alt").getRgb()))
  98. self._disabled_shader.setUniformValue("u_width", 50.0)
  99. self._disabled_shader.setUniformValue("u_opacity", 0.6)
  100. self.bind()
  101. tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True)
  102. disabled_batch = RenderBatch(self._disabled_shader)
  103. head_position = None # Indicates the current position of the print head
  104. nozzle_node = None
  105. for node in DepthFirstIterator(self._scene.getRoot()):
  106. if isinstance(node, ToolHandle):
  107. tool_handle_batch.addItem(node.getWorldTransformation(), mesh = node.getSolidMesh())
  108. elif isinstance(node, NozzleNode):
  109. nozzle_node = node
  110. nozzle_node.setVisible(False) # Don't set to true, we render it separately!
  111. elif getattr(node, "_outside_buildarea", False) and isinstance(node, SceneNode) and node.getMeshData() and node.isVisible() and not node.callDecoration("isNonPrintingMesh"):
  112. disabled_batch.addItem(node.getWorldTransformation(copy=False), node.getMeshData())
  113. elif isinstance(node, SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
  114. layer_data = node.callDecoration("getLayerData")
  115. if not layer_data:
  116. continue
  117. # Render all layers below a certain number as line mesh instead of vertices.
  118. if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())):
  119. start = 0
  120. end = 0
  121. current_polygon_offset = 0
  122. element_counts = layer_data.getElementCounts()
  123. for layer in sorted(element_counts.keys()):
  124. # In the current layer, we show just the indicated paths
  125. if layer == self._layer_view._current_layer_num:
  126. # We look for the position of the head, searching the point of the current path
  127. index = self._layer_view._current_path_num
  128. offset = 0
  129. for polygon in layer_data.getLayer(layer).polygons:
  130. # The size indicates all values in the two-dimension array, and the second dimension is
  131. # always size 3 because we have 3D points.
  132. if index >= polygon.data.size // 3 - offset:
  133. index -= polygon.data.size // 3 - offset
  134. offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon
  135. current_polygon_offset += 1
  136. continue
  137. # The head position is calculated and translated
  138. head_position = Vector(polygon.data[index+offset][0], polygon.data[index+offset][1], polygon.data[index+offset][2]) + node.getWorldPosition()
  139. break
  140. break
  141. end += layer_data.getLayer(layer).vertexCount
  142. if layer < self._layer_view._minimum_layer_num:
  143. start = end
  144. # Calculate the range of paths in the last layer. -- The type-change count is needed to keep the
  145. # vertex-indices aligned between the two different ways we represent polygons here.
  146. # Since there is one type per line, that could give a vertex two different types, if it's a vertex
  147. # where a type-chage occurs. However, the shader expects vertices to have only one type. In order to
  148. # fix this, those vertices are duplicated. This introduces a discrepancy that we have to take into
  149. # account, which is done by the type-change-count.
  150. layer = layer_data.getLayer(self._layer_view._current_layer_num)
  151. type_change_count = 0 if layer is None else layer.lineMeshCumulativeTypeChangeCount(max(self._layer_view._current_path_num - 1, 0))
  152. current_layer_start = end
  153. current_layer_end = current_layer_start + self._layer_view._current_path_num + current_polygon_offset + type_change_count
  154. # This uses glDrawRangeElements internally to only draw a certain range of lines.
  155. # All the layers but the current selected layer are rendered first
  156. if self._old_current_path != self._layer_view._current_path_num:
  157. self._current_shader = self._layer_shadow_shader
  158. self._switching_layers = False
  159. if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view._current_layer_num:
  160. self._current_shader = self._layer_shader
  161. self._switching_layers = True
  162. # The first line does not have a previous line: add a MoveCombingType in front for start detection
  163. # this way the first start of the layer can also be drawn
  164. prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveCombingType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]])
  165. # Remove the last element
  166. prev_line_types = prev_line_types[0:layer_data._attributes["line_types"]["value"].size]
  167. layer_data._attributes["prev_line_types"] = {'opengl_type': 'float', 'value': prev_line_types, 'opengl_name': 'a_prev_line_type'}
  168. layers_batch = RenderBatch(self._current_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (start, end), backface_cull = True)
  169. layers_batch.addItem(node.getWorldTransformation(), layer_data)
  170. layers_batch.render(self._scene.getActiveCamera())
  171. # Current selected layer is rendered
  172. current_layer_batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid, mode = RenderBatch.RenderMode.Lines, range = (current_layer_start, current_layer_end))
  173. current_layer_batch.addItem(node.getWorldTransformation(), layer_data)
  174. current_layer_batch.render(self._scene.getActiveCamera())
  175. self._old_current_layer = self._layer_view._current_layer_num
  176. self._old_current_path = self._layer_view._current_path_num
  177. # Create a new batch that is not range-limited
  178. batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid)
  179. if self._layer_view.getCurrentLayerMesh():
  180. batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerMesh())
  181. if self._layer_view.getCurrentLayerJumps():
  182. batch.addItem(node.getWorldTransformation(), self._layer_view.getCurrentLayerJumps())
  183. if len(batch.items) > 0:
  184. batch.render(self._scene.getActiveCamera())
  185. # The nozzle is drawn when once we know the correct position of the head,
  186. # but the user is not using the layer slider, and the compatibility mode is not enabled
  187. if not self._switching_layers and not self._compatibility_mode and self._layer_view.getActivity() and nozzle_node is not None:
  188. if head_position is not None:
  189. nozzle_node.setPosition(head_position)
  190. nozzle_batch = RenderBatch(self._nozzle_shader, type = RenderBatch.RenderType.Transparent)
  191. nozzle_batch.addItem(nozzle_node.getWorldTransformation(), mesh = nozzle_node.getMeshData())
  192. nozzle_batch.render(self._scene.getActiveCamera())
  193. if len(disabled_batch.items) > 0:
  194. disabled_batch.render(self._scene.getActiveCamera())
  195. # Render toolhandles on top of the layerview
  196. if len(tool_handle_batch.items) > 0:
  197. tool_handle_batch.render(self._scene.getActiveCamera())
  198. self.release()
  199. def _onSceneChanged(self, changed_object: SceneNode):
  200. if changed_object.callDecoration("getLayerData"): # Any layer data has changed.
  201. self._switching_layers = True
  202. self._old_current_layer = 0
  203. self._old_current_path = 0