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