LayerView.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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. import numpy
  22. ## View used to display g-code paths.
  23. class LayerView(View):
  24. def __init__(self):
  25. super().__init__()
  26. self._shader = None
  27. self._selection_shader = None
  28. self._num_layers = 0
  29. self._layer_percentage = 0 # what percentage of layers need to be shown (SLider gives value between 0 - 100)
  30. self._proxy = LayerViewProxy.LayerViewProxy()
  31. self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
  32. self._max_layers = 0
  33. self._current_layer_num = 0
  34. self._current_layer_mesh = None
  35. self._current_layer_jumps = None
  36. self._top_layers_job = None
  37. self._activity = False
  38. Preferences.getInstance().addPreference("view/top_layer_count", 5)
  39. Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
  40. self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
  41. self._top_layer_timer = QTimer()
  42. self._top_layer_timer.setInterval(50)
  43. self._top_layer_timer.setSingleShot(True)
  44. self._top_layer_timer.timeout.connect(self._startUpdateTopLayers)
  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:
  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.resetLayerData()
  105. self._top_layer_timer.start()
  106. self.currentLayerNumChanged.emit()
  107. def calculateMaxLayers(self):
  108. scene = self.getController().getScene()
  109. renderer = self.getRenderer() # TODO: @UnusedVariable
  110. self._activity = True
  111. self._old_max_layers = self._max_layers
  112. ## Recalculate num max layers
  113. new_max_layers = 0
  114. for node in DepthFirstIterator(scene.getRoot()):
  115. layer_data = node.callDecoration("getLayerData")
  116. if not layer_data:
  117. continue
  118. if new_max_layers < len(layer_data.getLayers()):
  119. new_max_layers = len(layer_data.getLayers()) - 1
  120. if new_max_layers > 0 and new_max_layers != self._old_max_layers:
  121. self._max_layers = new_max_layers
  122. # The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
  123. # if it's the largest value. If we don't do this, we can have a slider block outside of the
  124. # slider.
  125. if new_max_layers > self._current_layer_num:
  126. self.maxLayersChanged.emit()
  127. self.setLayer(int(self._max_layers))
  128. else:
  129. self.setLayer(int(self._max_layers))
  130. self.maxLayersChanged.emit()
  131. self._top_layer_timer.start()
  132. maxLayersChanged = Signal()
  133. currentLayerNumChanged = Signal()
  134. ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
  135. # as this caused some issues.
  136. def getProxy(self, engine, script_engine):
  137. return self._proxy
  138. def endRendering(self):
  139. pass
  140. def event(self, event):
  141. modifiers = QApplication.keyboardModifiers()
  142. ctrl_is_active = modifiers == Qt.ControlModifier
  143. if event.type == Event.KeyPressEvent and ctrl_is_active:
  144. if event.key == KeyEvent.UpKey:
  145. self.setLayer(self._current_layer_num + 1)
  146. return True
  147. if event.key == KeyEvent.DownKey:
  148. self.setLayer(self._current_layer_num - 1)
  149. return True
  150. def _startUpdateTopLayers(self):
  151. if self._top_layers_job:
  152. self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh)
  153. self._top_layers_job.cancel()
  154. self.setBusy(True)
  155. self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
  156. self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
  157. self._top_layers_job.start()
  158. def _updateCurrentLayerMesh(self, job):
  159. self.setBusy(False)
  160. if not job.getResult():
  161. return
  162. self._current_layer_mesh = job.getResult().get("layers")
  163. self._current_layer_jumps = job.getResult().get("jumps")
  164. self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
  165. self._top_layers_job = None
  166. def _onPreferencesChanged(self, preference):
  167. if preference != "view/top_layer_count":
  168. return
  169. self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
  170. self.resetLayerData()
  171. self._top_layer_timer.start()
  172. class _CreateTopLayersJob(Job):
  173. def __init__(self, scene, layer_number, solid_layers):
  174. super().__init__()
  175. self._scene = scene
  176. self._layer_number = layer_number
  177. self._solid_layers = solid_layers
  178. self._cancel = False
  179. def run(self):
  180. layer_data = None
  181. for node in DepthFirstIterator(self._scene.getRoot()):
  182. layer_data = node.callDecoration("getLayerData")
  183. if layer_data:
  184. break
  185. if self._cancel or not layer_data:
  186. return
  187. layer_mesh = MeshBuilder()
  188. for i in range(self._solid_layers):
  189. layer_number = self._layer_number - i
  190. if layer_number < 0:
  191. continue
  192. try:
  193. layer = layer_data.getLayer(layer_number).createMesh()
  194. except Exception as e:
  195. print(e)
  196. return
  197. if not layer or layer.getVertices() is None:
  198. continue
  199. layer_mesh.addIndices(layer_mesh._vertex_count+layer.getIndices())
  200. layer_mesh.addVertices(layer.getVertices())
  201. # Scale layer color by a brightness factor based on the current layer number
  202. # This will result in a range of 0.5 - 1.0 to multiply colors by.
  203. brightness = numpy.ones((1,4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
  204. brightness[0, 3] = 1.0;
  205. layer_mesh.addColors(layer.getColors() * brightness)
  206. if self._cancel:
  207. return
  208. Job.yieldThread()
  209. if self._cancel:
  210. return
  211. Job.yieldThread()
  212. jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
  213. if not jump_mesh or jump_mesh.getVertices() is None:
  214. jump_mesh = None
  215. self.setResult({ "layers": layer_mesh.build(), "jumps": jump_mesh })
  216. def cancel(self):
  217. self._cancel = True
  218. super().cancel()