LayerView.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  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.Scene.SceneNode import SceneNode
  15. from UM.View.RenderBatch import RenderBatch
  16. from UM.View.GL.OpenGL import OpenGL
  17. from UM.Message import Message
  18. from UM.Application import Application
  19. from cura.ConvexHullNode import ConvexHullNode
  20. from PyQt5.QtCore import Qt, QTimer
  21. from PyQt5.QtWidgets import QApplication
  22. from . import LayerViewProxy
  23. from UM.i18n import i18nCatalog
  24. catalog = i18nCatalog("cura")
  25. import numpy
  26. ## View used to display g-code paths.
  27. class LayerView(View):
  28. def __init__(self):
  29. super().__init__()
  30. self._shader = None
  31. self._ghost_shader = None
  32. self._num_layers = 0
  33. self._layer_percentage = 0 # what percentage of layers need to be shown (Slider gives value between 0 - 100)
  34. self._proxy = LayerViewProxy.LayerViewProxy()
  35. self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
  36. self._max_layers = 0
  37. self._current_layer_num = 0
  38. self._current_layer_mesh = None
  39. self._current_layer_jumps = None
  40. self._top_layers_job = None
  41. self._activity = False
  42. self._old_max_layers = 0
  43. self._global_container_stack = None
  44. Preferences.getInstance().addPreference("view/top_layer_count", 5)
  45. Preferences.getInstance().addPreference("view/only_show_top_layers", False)
  46. Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
  47. self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
  48. self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
  49. self._busy = False
  50. self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"))
  51. def getActivity(self):
  52. return self._activity
  53. def getCurrentLayer(self):
  54. return self._current_layer_num
  55. def _onSceneChanged(self, node):
  56. self.calculateMaxLayers()
  57. def getMaxLayers(self):
  58. return self._max_layers
  59. busyChanged = Signal()
  60. def isBusy(self):
  61. return self._busy
  62. def setBusy(self, busy):
  63. if busy != self._busy:
  64. self._busy = busy
  65. self.busyChanged.emit()
  66. def resetLayerData(self):
  67. self._current_layer_mesh = None
  68. self._current_layer_jumps = None
  69. def beginRendering(self):
  70. scene = self.getController().getScene()
  71. renderer = self.getRenderer()
  72. if not self._ghost_shader:
  73. self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
  74. self._ghost_shader.setUniformValue("u_color", Color(0, 0, 0, 64))
  75. for node in DepthFirstIterator(scene.getRoot()):
  76. # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
  77. # However, it is somewhat relevant when the node is selected, so do render it then.
  78. if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
  79. continue
  80. if not node.render(renderer):
  81. if node.getMeshData() and node.isVisible():
  82. renderer.queueNode(node,
  83. shader = self._ghost_shader,
  84. type = RenderBatch.RenderType.Transparent )
  85. for node in DepthFirstIterator(scene.getRoot()):
  86. if type(node) is SceneNode:
  87. if node.getMeshData() and node.isVisible():
  88. layer_data = node.callDecoration("getLayerData")
  89. if not layer_data:
  90. continue
  91. # Render all layers below a certain number as line mesh instead of vertices.
  92. if self._current_layer_num - self._solid_layers > -1 and not self._only_show_top_layers:
  93. start = 0
  94. end = 0
  95. element_counts = layer_data.getElementCounts()
  96. for layer, counts in element_counts.items():
  97. if layer + self._solid_layers > self._current_layer_num:
  98. break
  99. end += counts
  100. # This uses glDrawRangeElements internally to only draw a certain range of lines.
  101. renderer.queueNode(node, mesh = layer_data, mode = RenderBatch.RenderMode.Lines, range = (start, end))
  102. if self._current_layer_mesh:
  103. renderer.queueNode(node, mesh = self._current_layer_mesh)
  104. if self._current_layer_jumps:
  105. renderer.queueNode(node, mesh = self._current_layer_jumps)
  106. def setLayer(self, value):
  107. if self._current_layer_num != value:
  108. self._current_layer_num = value
  109. if self._current_layer_num < 0:
  110. self._current_layer_num = 0
  111. if self._current_layer_num > self._max_layers:
  112. self._current_layer_num = self._max_layers
  113. self._startUpdateTopLayers()
  114. self.currentLayerNumChanged.emit()
  115. def calculateMaxLayers(self):
  116. scene = self.getController().getScene()
  117. self._activity = True
  118. self._old_max_layers = self._max_layers
  119. ## Recalculate num max layers
  120. new_max_layers = 0
  121. for node in DepthFirstIterator(scene.getRoot()):
  122. layer_data = node.callDecoration("getLayerData")
  123. if not layer_data:
  124. continue
  125. if new_max_layers < len(layer_data.getLayers()):
  126. new_max_layers = len(layer_data.getLayers()) - 1
  127. if new_max_layers > 0 and new_max_layers != self._old_max_layers:
  128. self._max_layers = new_max_layers
  129. # The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
  130. # if it's the largest value. If we don't do this, we can have a slider block outside of the
  131. # slider.
  132. if new_max_layers > self._current_layer_num:
  133. self.maxLayersChanged.emit()
  134. self.setLayer(int(self._max_layers))
  135. else:
  136. self.setLayer(int(self._max_layers))
  137. self.maxLayersChanged.emit()
  138. self._startUpdateTopLayers()
  139. maxLayersChanged = Signal()
  140. currentLayerNumChanged = Signal()
  141. ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
  142. # as this caused some issues.
  143. def getProxy(self, engine, script_engine):
  144. return self._proxy
  145. def endRendering(self):
  146. pass
  147. def event(self, event):
  148. modifiers = QApplication.keyboardModifiers()
  149. ctrl_is_active = modifiers == Qt.ControlModifier
  150. if event.type == Event.KeyPressEvent and ctrl_is_active:
  151. if event.key == KeyEvent.UpKey:
  152. self.setLayer(self._current_layer_num + 1)
  153. return True
  154. if event.key == KeyEvent.DownKey:
  155. self.setLayer(self._current_layer_num - 1)
  156. return True
  157. if event.type == Event.ViewActivateEvent:
  158. Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
  159. self._onGlobalStackChanged()
  160. elif event.type == Event.ViewDeactivateEvent:
  161. self._wireprint_warning_message.hide()
  162. Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
  163. if self._global_container_stack:
  164. self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
  165. def _onGlobalStackChanged(self):
  166. if self._global_container_stack:
  167. self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
  168. self._global_container_stack = Application.getInstance().getGlobalContainerStack()
  169. if self._global_container_stack:
  170. self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
  171. self._onPropertyChanged("wireframe_enabled", "value")
  172. else:
  173. self._wireprint_warning_message.hide()
  174. def _onPropertyChanged(self, key, property_name):
  175. if key == "wireframe_enabled" and property_name == "value":
  176. if self._global_container_stack.getProperty("wireframe_enabled", "value"):
  177. self._wireprint_warning_message.show()
  178. else:
  179. self._wireprint_warning_message.hide()
  180. def _startUpdateTopLayers(self):
  181. if self._top_layers_job:
  182. self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh)
  183. self._top_layers_job.cancel()
  184. self.setBusy(True)
  185. self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
  186. self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
  187. self._top_layers_job.start()
  188. def _updateCurrentLayerMesh(self, job):
  189. self.setBusy(False)
  190. if not job.getResult():
  191. return
  192. self.resetLayerData() # Reset the layer data only when job is done. Doing it now prevents "blinking" data.
  193. self._current_layer_mesh = job.getResult().get("layers")
  194. self._current_layer_jumps = job.getResult().get("jumps")
  195. self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
  196. self._top_layers_job = None
  197. def _onPreferencesChanged(self, preference):
  198. if preference != "view/top_layer_count" and preference != "view/only_show_top_layers":
  199. return
  200. self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
  201. self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
  202. self._startUpdateTopLayers()
  203. class _CreateTopLayersJob(Job):
  204. def __init__(self, scene, layer_number, solid_layers):
  205. super().__init__()
  206. self._scene = scene
  207. self._layer_number = layer_number
  208. self._solid_layers = solid_layers
  209. self._cancel = False
  210. def run(self):
  211. layer_data = None
  212. for node in DepthFirstIterator(self._scene.getRoot()):
  213. layer_data = node.callDecoration("getLayerData")
  214. if layer_data:
  215. break
  216. if self._cancel or not layer_data:
  217. return
  218. layer_mesh = MeshBuilder()
  219. for i in range(self._solid_layers):
  220. layer_number = self._layer_number - i
  221. if layer_number < 0:
  222. continue
  223. try:
  224. layer = layer_data.getLayer(layer_number).createMesh()
  225. except Exception:
  226. Logger.logException("w", "An exception occurred while creating layer mesh.")
  227. return
  228. if not layer or layer.getVertices() is None:
  229. continue
  230. layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices())
  231. layer_mesh.addVertices(layer.getVertices())
  232. # Scale layer color by a brightness factor based on the current layer number
  233. # This will result in a range of 0.5 - 1.0 to multiply colors by.
  234. brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
  235. brightness[0, 3] = 1.0
  236. layer_mesh.addColors(layer.getColors() * brightness)
  237. if self._cancel:
  238. return
  239. Job.yieldThread()
  240. if self._cancel:
  241. return
  242. Job.yieldThread()
  243. jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
  244. if not jump_mesh or jump_mesh.getVertices() is None:
  245. jump_mesh = None
  246. self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
  247. def cancel(self):
  248. self._cancel = True
  249. super().cancel()