LayerView.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  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.MeshBuilder import MeshBuilder
  11. from UM.Job import Job
  12. from UM.Preferences import Preferences
  13. from UM.Logger import Logger
  14. from UM.View.RenderBatch import RenderBatch
  15. from UM.View.GL.OpenGL import OpenGL
  16. from cura.ConvexHullNode import ConvexHullNode
  17. from PyQt5.QtCore import Qt, QTimer
  18. from PyQt5.QtWidgets import QApplication
  19. from . import LayerViewProxy
  20. from UM.i18n import i18nCatalog
  21. catalog = i18nCatalog("cura")
  22. import numpy
  23. ## View used to display g-code paths.
  24. class LayerView(View):
  25. def __init__(self):
  26. super().__init__()
  27. self._shader = None
  28. self._selection_shader = None
  29. self._num_layers = 0
  30. self._layer_percentage = 0 # what percentage of layers need to be shown (Slider gives value between 0 - 100)
  31. self._proxy = LayerViewProxy.LayerViewProxy()
  32. self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
  33. self._max_layers = 0
  34. self._current_layer_num = 0
  35. self._current_layer_mesh = None
  36. self._current_layer_jumps = None
  37. self._top_layers_job = None
  38. self._activity = False
  39. self._old_max_layers = 0
  40. Preferences.getInstance().addPreference("view/top_layer_count", 5)
  41. Preferences.getInstance().addPreference("view/only_show_top_layers", False)
  42. Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
  43. self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
  44. self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
  45. self._busy = False
  46. def getActivity(self):
  47. return self._activity
  48. def getCurrentLayer(self):
  49. return self._current_layer_num
  50. def _onSceneChanged(self, node):
  51. self.calculateMaxLayers()
  52. def getMaxLayers(self):
  53. return self._max_layers
  54. busyChanged = Signal()
  55. def isBusy(self):
  56. return self._busy
  57. def setBusy(self, busy):
  58. if busy != self._busy:
  59. self._busy = busy
  60. self.busyChanged.emit()
  61. def resetLayerData(self):
  62. self._current_layer_mesh = None
  63. self._current_layer_jumps = None
  64. def beginRendering(self):
  65. scene = self.getController().getScene()
  66. renderer = self.getRenderer()
  67. if not self._selection_shader:
  68. self._selection_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
  69. self._selection_shader.setUniformValue("u_color", Color(32, 32, 32, 128))
  70. for node in DepthFirstIterator(scene.getRoot()):
  71. # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
  72. # However, it is somewhat relevant when the node is selected, so do render it then.
  73. if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
  74. continue
  75. if not node.render(renderer):
  76. if node.getMeshData() and node.isVisible():
  77. if Selection.isSelected(node):
  78. renderer.queueNode(node, transparent = True, shader = self._selection_shader)
  79. layer_data = node.callDecoration("getLayerData")
  80. if not layer_data:
  81. continue
  82. # Render all layers below a certain number as line mesh instead of vertices.
  83. if self._current_layer_num - self._solid_layers > -1 and not self._only_show_top_layers:
  84. start = 0
  85. end = 0
  86. element_counts = layer_data.getElementCounts()
  87. for layer, counts in element_counts.items():
  88. if layer + self._solid_layers > self._current_layer_num:
  89. break
  90. end += counts
  91. # This uses glDrawRangeElements internally to only draw a certain range of lines.
  92. renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, range = (start, end))
  93. if self._current_layer_mesh:
  94. renderer.queueNode(node, mesh = self._current_layer_mesh)
  95. if self._current_layer_jumps:
  96. renderer.queueNode(node, mesh = self._current_layer_jumps)
  97. def setLayer(self, value):
  98. if self._current_layer_num != value:
  99. self._current_layer_num = value
  100. if self._current_layer_num < 0:
  101. self._current_layer_num = 0
  102. if self._current_layer_num > self._max_layers:
  103. self._current_layer_num = self._max_layers
  104. self._startUpdateTopLayers()
  105. self.currentLayerNumChanged.emit()
  106. def calculateMaxLayers(self):
  107. scene = self.getController().getScene()
  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._startUpdateTopLayers()
  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.resetLayerData() # Reset the layer data only when job is done. Doing it now prevents "blinking" data.
  161. self._current_layer_mesh = job.getResult().get("layers")
  162. self._current_layer_jumps = job.getResult().get("jumps")
  163. self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
  164. self._top_layers_job = None
  165. def _onPreferencesChanged(self, preference):
  166. if preference != "view/top_layer_count" and preference != "view/only_show_top_layers":
  167. return
  168. self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
  169. self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
  170. self._startUpdateTopLayers()
  171. class _CreateTopLayersJob(Job):
  172. def __init__(self, scene, layer_number, solid_layers):
  173. super().__init__()
  174. self._scene = scene
  175. self._layer_number = layer_number
  176. self._solid_layers = solid_layers
  177. self._cancel = False
  178. def run(self):
  179. layer_data = None
  180. for node in DepthFirstIterator(self._scene.getRoot()):
  181. layer_data = node.callDecoration("getLayerData")
  182. if layer_data:
  183. break
  184. if self._cancel or not layer_data:
  185. return
  186. layer_mesh = MeshBuilder()
  187. for i in range(self._solid_layers):
  188. layer_number = self._layer_number - i
  189. if layer_number < 0:
  190. continue
  191. try:
  192. layer = layer_data.getLayer(layer_number).createMesh()
  193. except Exception:
  194. Logger.logException("w", "An exception occurred while creating layer mesh.")
  195. return
  196. if not layer or layer.getVertices() is None:
  197. continue
  198. layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices())
  199. layer_mesh.addVertices(layer.getVertices())
  200. # Scale layer color by a brightness factor based on the current layer number
  201. # This will result in a range of 0.5 - 1.0 to multiply colors by.
  202. brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
  203. brightness[0, 3] = 1.0
  204. layer_mesh.addColors(layer.getColors() * brightness)
  205. if self._cancel:
  206. return
  207. Job.yieldThread()
  208. if self._cancel:
  209. return
  210. Job.yieldThread()
  211. jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
  212. if not jump_mesh or jump_mesh.getVertices() is None:
  213. jump_mesh = None
  214. self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
  215. def cancel(self):
  216. self._cancel = True
  217. super().cancel()