LayerView.py 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from UM.View.View import View
  4. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  5. from UM.Resources import Resources
  6. from UM.Event import Event, KeyEvent
  7. from UM.Signal import Signal
  8. from UM.Scene.Selection import Selection
  9. from UM.Math.Color import Color
  10. from UM.Mesh.MeshData import MeshData
  11. from UM.Job import Job
  12. from UM.View.RenderBatch import RenderBatch
  13. from UM.View.GL.OpenGL import OpenGL
  14. from cura.ConvexHullNode import ConvexHullNode
  15. from PyQt5.QtCore import Qt, QTimer
  16. from PyQt5.QtWidgets import QApplication
  17. from . import LayerViewProxy
  18. from UM.i18n import i18nCatalog
  19. catalog = i18nCatalog("cura")
  20. ## View used to display g-code paths.
  21. class LayerView(View):
  22. def __init__(self):
  23. super().__init__()
  24. self._shader = None
  25. self._selection_shader = None
  26. self._num_layers = 0
  27. self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100)
  28. self._proxy = LayerViewProxy.LayerViewProxy()
  29. self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
  30. self._max_layers = 10
  31. self._current_layer_num = 10
  32. self._current_layer_mesh = None
  33. self._current_layer_jumps = None
  34. self._top_layers_job = None
  35. self._activity = False
  36. self._solid_layers = 5
  37. self._top_layer_timer = QTimer()
  38. self._top_layer_timer.setInterval(50)
  39. self._top_layer_timer.setSingleShot(True)
  40. self._top_layer_timer.timeout.connect(self._startUpdateTopLayers)
  41. self._busy = False
  42. def getActivity(self):
  43. return self._activity
  44. def getCurrentLayer(self):
  45. return self._current_layer_num
  46. def _onSceneChanged(self, node):
  47. self.calculateMaxLayers()
  48. def getMaxLayers(self):
  49. return self._max_layers
  50. busyChanged = Signal()
  51. def isBusy(self):
  52. return self._busy
  53. def setBusy(self, busy):
  54. if busy != self._busy:
  55. self._busy = busy
  56. self.busyChanged.emit()
  57. def resetLayerData(self):
  58. self._current_layer_mesh = None
  59. self._current_layer_jumps = None
  60. def beginRendering(self):
  61. scene = self.getController().getScene()
  62. renderer = self.getRenderer()
  63. if not self._selection_shader:
  64. self._selection_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
  65. self._selection_shader.setUniformValue("u_color", Color(32, 32, 32, 128))
  66. for node in DepthFirstIterator(scene.getRoot()):
  67. # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
  68. # However, it is somewhat relevant when the node is selected, so do render it then.
  69. if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
  70. continue
  71. if not node.render(renderer):
  72. if node.getMeshData() and node.isVisible():
  73. if Selection.isSelected(node):
  74. renderer.queueNode(node, transparent = True, shader = self._selection_shader)
  75. layer_data = node.callDecoration("getLayerData")
  76. if not layer_data:
  77. continue
  78. # Render all layers below a certain number as line mesh instead of vertices.
  79. if self._current_layer_num - self._solid_layers > -1:
  80. start = 0
  81. end = 0
  82. element_counts = layer_data.getElementCounts()
  83. for layer, counts in element_counts.items():
  84. if layer + self._solid_layers > self._current_layer_num:
  85. break
  86. end += counts
  87. # This uses glDrawRangeElements internally to only draw a certain range of lines.
  88. renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, range = (start, end))
  89. if self._current_layer_mesh:
  90. renderer.queueNode(node, mesh = self._current_layer_mesh)
  91. if self._current_layer_jumps:
  92. renderer.queueNode(node, mesh = self._current_layer_jumps)
  93. def setLayer(self, value):
  94. if self._current_layer_num != value:
  95. self._current_layer_num = value
  96. if self._current_layer_num < 0:
  97. self._current_layer_num = 0
  98. if self._current_layer_num > self._max_layers:
  99. self._current_layer_num = self._max_layers
  100. self._current_layer_mesh = None
  101. self._current_layer_jumps = None
  102. self._top_layer_timer.start()
  103. self.currentLayerNumChanged.emit()
  104. currentLayerNumChanged = Signal()
  105. def calculateMaxLayers(self):
  106. scene = self.getController().getScene()
  107. renderer = self.getRenderer() # TODO: Unused variable
  108. self._activity = True
  109. self._old_max_layers = self._max_layers
  110. ## Recalculate num max layers
  111. new_max_layers = 0
  112. for node in DepthFirstIterator(scene.getRoot()):
  113. layer_data = node.callDecoration("getLayerData")
  114. if not layer_data:
  115. continue
  116. if new_max_layers < len(layer_data.getLayers()):
  117. new_max_layers = len(layer_data.getLayers()) - 1
  118. if new_max_layers > 0 and new_max_layers != self._old_max_layers:
  119. self._max_layers = new_max_layers
  120. # The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
  121. # if it's the largest value. If we don't do this, we can have a slider block outside of the
  122. # slider.
  123. if new_max_layers > self._current_layer_num:
  124. self.maxLayersChanged.emit()
  125. self.setLayer(int(self._max_layers))
  126. else:
  127. self.setLayer(int(self._max_layers))
  128. self.maxLayersChanged.emit()
  129. self._top_layer_timer.start()
  130. maxLayersChanged = Signal()
  131. currentLayerNumChanged = Signal()
  132. ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
  133. # as this caused some issues.
  134. def getProxy(self, engine, script_engine):
  135. return self._proxy
  136. def endRendering(self):
  137. pass
  138. def event(self, event):
  139. modifiers = QApplication.keyboardModifiers()
  140. ctrl_is_active = modifiers == Qt.ControlModifier
  141. if event.type == Event.KeyPressEvent and ctrl_is_active:
  142. if event.key == KeyEvent.UpKey:
  143. self.setLayer(self._current_layer_num + 1)
  144. return True
  145. if event.key == KeyEvent.DownKey:
  146. self.setLayer(self._current_layer_num - 1)
  147. return True
  148. def _startUpdateTopLayers(self):
  149. if self._top_layers_job:
  150. self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh)
  151. self._top_layers_job.cancel()
  152. self.setBusy(True)
  153. self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
  154. self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
  155. self._top_layers_job.start()
  156. def _updateCurrentLayerMesh(self, job):
  157. self.setBusy(False)
  158. if not job.getResult():
  159. return
  160. self._current_layer_mesh = job.getResult().get("layers")
  161. self._current_layer_jumps = job.getResult().get("jumps")
  162. self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
  163. self._top_layers_job = None
  164. class _CreateTopLayersJob(Job):
  165. def __init__(self, scene, layer_number, solid_layers):
  166. super().__init__()
  167. self._scene = scene
  168. self._layer_number = layer_number
  169. self._solid_layers = solid_layers
  170. self._cancel = False
  171. def run(self):
  172. layer_data = None
  173. for node in DepthFirstIterator(self._scene.getRoot()):
  174. layer_data = node.callDecoration("getLayerData")
  175. if layer_data:
  176. break
  177. if self._cancel or not layer_data:
  178. return
  179. layer_mesh = MeshData()
  180. for i in range(self._solid_layers):
  181. layer_number = self._layer_number - i
  182. if layer_number < 0:
  183. continue
  184. try:
  185. layer = layer_data.getLayer(layer_number).createMesh()
  186. except Exception as e:
  187. print(e)
  188. return
  189. if not layer or layer.getVertices() is None:
  190. continue
  191. layer_mesh.addVertices(layer.getVertices())
  192. # Scale layer color by a brightness factor based on the current layer number
  193. # This will result in a range of 0.5 - 1.0 to multiply colors by.
  194. brightness = (2.0 - (i / self._solid_layers)) / 2.0
  195. layer_mesh.addColors(layer.getColors() * brightness)
  196. if self._cancel:
  197. return
  198. Job.yieldThread()
  199. if self._cancel:
  200. return
  201. Job.yieldThread()
  202. jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
  203. if not jump_mesh or jump_mesh.getVertices() is None:
  204. jump_mesh = None
  205. self.setResult({ "layers": layer_mesh, "jumps": jump_mesh })
  206. def cancel(self):
  207. self._cancel = True
  208. super().cancel()