LayerView.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Cura is released under the terms of the AGPLv3 or higher.
  3. from UM.PluginRegistry import PluginRegistry
  4. from UM.View.View import View
  5. from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
  6. from UM.Resources import Resources
  7. from UM.Event import Event, KeyEvent
  8. from UM.Signal import Signal
  9. from UM.Scene.Selection import Selection
  10. from UM.Math.Color import Color
  11. from UM.Mesh.MeshBuilder import MeshBuilder
  12. from UM.Job import Job
  13. from UM.Preferences import Preferences
  14. from UM.Logger import Logger
  15. from UM.Scene.SceneNode import SceneNode
  16. from UM.View.RenderBatch import RenderBatch
  17. from UM.View.GL.OpenGL import OpenGL
  18. from UM.Message import Message
  19. from UM.Application import Application
  20. from cura.ConvexHullNode import ConvexHullNode
  21. from PyQt5.QtCore import Qt, QTimer
  22. from PyQt5.QtWidgets import QApplication
  23. from . import LayerViewProxy
  24. from UM.i18n import i18nCatalog
  25. catalog = i18nCatalog("cura")
  26. from . import LayerPass
  27. import numpy
  28. import os.path
  29. ## View used to display g-code paths.
  30. class LayerView(View):
  31. def __init__(self):
  32. super().__init__()
  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. self._busy = False
  41. self._ghost_shader = None
  42. self._layer_pass = None
  43. self._composite_pass = None
  44. self._old_layer_bindings = None
  45. self._layerview_composite_shader = None
  46. self._old_composite_shader = None
  47. self._global_container_stack = None
  48. self._proxy = LayerViewProxy.LayerViewProxy()
  49. self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
  50. self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed
  51. self._only_color_active_extruder = True
  52. self._extruder_opacity = [1.0, 1.0, 1.0, 1.0]
  53. self._show_travel_moves = 0
  54. self._show_support = 1
  55. self._show_adhesion = 1
  56. self._show_skin = 1
  57. self._show_infill = 1
  58. Preferences.getInstance().addPreference("view/top_layer_count", 5)
  59. Preferences.getInstance().addPreference("view/only_show_top_layers", False)
  60. Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
  61. self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
  62. self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
  63. self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"))
  64. def getActivity(self):
  65. return self._activity
  66. def getLayerPass(self):
  67. if not self._layer_pass:
  68. # Currently the RenderPass constructor requires a size > 0
  69. # This should be fixed in RenderPass's constructor.
  70. self._layer_pass = LayerPass.LayerPass(1, 1)
  71. self._layer_pass.setLayerView(self)
  72. self.getRenderer().addRenderPass(self._layer_pass)
  73. return self._layer_pass
  74. def getCurrentLayer(self):
  75. return self._current_layer_num
  76. def _onSceneChanged(self, node):
  77. self.calculateMaxLayers()
  78. def getMaxLayers(self):
  79. return self._max_layers
  80. busyChanged = Signal()
  81. def isBusy(self):
  82. return self._busy
  83. def setBusy(self, busy):
  84. if busy != self._busy:
  85. self._busy = busy
  86. self.busyChanged.emit()
  87. def resetLayerData(self):
  88. self._current_layer_mesh = None
  89. self._current_layer_jumps = None
  90. def beginRendering(self):
  91. scene = self.getController().getScene()
  92. renderer = self.getRenderer()
  93. if not self._ghost_shader:
  94. self._ghost_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "color.shader"))
  95. self._ghost_shader.setUniformValue("u_color", Color(32, 32, 32, 96))
  96. for node in DepthFirstIterator(scene.getRoot()):
  97. # We do not want to render ConvexHullNode as it conflicts with the bottom layers.
  98. # However, it is somewhat relevant when the node is selected, so do render it then.
  99. if type(node) is ConvexHullNode and not Selection.isSelected(node.getWatchedNode()):
  100. continue
  101. if not node.render(renderer):
  102. if node.getMeshData() and node.isVisible():
  103. renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
  104. def setLayer(self, value):
  105. if self._current_layer_num != value:
  106. self._current_layer_num = value
  107. if self._current_layer_num < 0:
  108. self._current_layer_num = 0
  109. if self._current_layer_num > self._max_layers:
  110. self._current_layer_num = self._max_layers
  111. self._startUpdateTopLayers()
  112. self.currentLayerNumChanged.emit()
  113. def setLayerViewType(self, layer_view_type):
  114. self._layer_view_type = layer_view_type
  115. self.currentLayerNumChanged.emit()
  116. def getLayerViewType(self):
  117. return self._layer_view_type
  118. def setOnlyColorActiveExtruder(self, only_color_active_extruder):
  119. self._only_color_active_extruder = only_color_active_extruder
  120. self.currentLayerNumChanged.emit()
  121. def getOnlyColorActiveExtruder(self):
  122. return self._only_color_active_extruder
  123. def setExtruderOpacity(self, extruder_nr, opacity):
  124. self._extruder_opacity[extruder_nr] = opacity
  125. self.currentLayerNumChanged.emit()
  126. def getExtruderOpacities(self):
  127. return self._extruder_opacity
  128. def setShowTravelMoves(self, show):
  129. self._show_travel_moves = show
  130. self.currentLayerNumChanged.emit()
  131. def getShowTravelMoves(self):
  132. return self._show_travel_moves
  133. def setShowSupport(self, show):
  134. self._show_support = show
  135. self.currentLayerNumChanged.emit()
  136. def getShowSupport(self):
  137. return self._show_support
  138. def setShowAdhesion(self, show):
  139. self._show_adhesion = show
  140. self.currentLayerNumChanged.emit()
  141. def getShowAdhesion(self):
  142. return self._show_adhesion
  143. def setShowSkin(self, show):
  144. self._show_skin = show
  145. self.currentLayerNumChanged.emit()
  146. def getShowSkin(self):
  147. return self._show_skin
  148. def setShowInfill(self, show):
  149. self._show_infill = show
  150. self.currentLayerNumChanged.emit()
  151. def getShowInfill(self):
  152. return self._show_infill
  153. def calculateMaxLayers(self):
  154. scene = self.getController().getScene()
  155. self._activity = True
  156. self._old_max_layers = self._max_layers
  157. ## Recalculate num max layers
  158. new_max_layers = 0
  159. for node in DepthFirstIterator(scene.getRoot()):
  160. layer_data = node.callDecoration("getLayerData")
  161. if not layer_data:
  162. continue
  163. if new_max_layers < len(layer_data.getLayers()):
  164. new_max_layers = len(layer_data.getLayers()) - 1
  165. if new_max_layers > 0 and new_max_layers != self._old_max_layers:
  166. self._max_layers = new_max_layers
  167. # The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first
  168. # if it's the largest value. If we don't do this, we can have a slider block outside of the
  169. # slider.
  170. if new_max_layers > self._current_layer_num:
  171. self.maxLayersChanged.emit()
  172. self.setLayer(int(self._max_layers))
  173. else:
  174. self.setLayer(int(self._max_layers))
  175. self.maxLayersChanged.emit()
  176. self._startUpdateTopLayers()
  177. maxLayersChanged = Signal()
  178. currentLayerNumChanged = Signal()
  179. ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created
  180. # as this caused some issues.
  181. def getProxy(self, engine, script_engine):
  182. return self._proxy
  183. def endRendering(self):
  184. pass
  185. def event(self, event):
  186. modifiers = QApplication.keyboardModifiers()
  187. ctrl_is_active = modifiers == Qt.ControlModifier
  188. if event.type == Event.KeyPressEvent and ctrl_is_active:
  189. if event.key == KeyEvent.UpKey:
  190. self.setLayer(self._current_layer_num + 1)
  191. return True
  192. if event.key == KeyEvent.DownKey:
  193. self.setLayer(self._current_layer_num - 1)
  194. return True
  195. if event.type == Event.ViewActivateEvent:
  196. # Make sure the LayerPass is created
  197. self.getLayerPass()
  198. Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
  199. self._onGlobalStackChanged()
  200. if not self._layerview_composite_shader:
  201. self._layerview_composite_shader = OpenGL.getInstance().createShaderProgram(os.path.join(PluginRegistry.getInstance().getPluginPath("LayerView"), "layerview_composite.shader"))
  202. if not self._composite_pass:
  203. self._composite_pass = self.getRenderer().getRenderPass("composite")
  204. self._old_layer_bindings = self._composite_pass.getLayerBindings()[:] # make a copy so we can restore to it later
  205. self._composite_pass.getLayerBindings().append("layerview")
  206. self._old_composite_shader = self._composite_pass.getCompositeShader()
  207. self._composite_pass.setCompositeShader(self._layerview_composite_shader)
  208. elif event.type == Event.ViewDeactivateEvent:
  209. self._wireprint_warning_message.hide()
  210. Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
  211. if self._global_container_stack:
  212. self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
  213. self._composite_pass.setLayerBindings(self._old_layer_bindings)
  214. self._composite_pass.setCompositeShader(self._old_composite_shader)
  215. def _onGlobalStackChanged(self):
  216. if self._global_container_stack:
  217. self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
  218. self._global_container_stack = Application.getInstance().getGlobalContainerStack()
  219. if self._global_container_stack:
  220. self._global_container_stack.propertyChanged.connect(self._onPropertyChanged)
  221. self._onPropertyChanged("wireframe_enabled", "value")
  222. else:
  223. self._wireprint_warning_message.hide()
  224. def _onPropertyChanged(self, key, property_name):
  225. if key == "wireframe_enabled" and property_name == "value":
  226. if self._global_container_stack.getProperty("wireframe_enabled", "value"):
  227. self._wireprint_warning_message.show()
  228. else:
  229. self._wireprint_warning_message.hide()
  230. def _startUpdateTopLayers(self):
  231. if self._top_layers_job:
  232. self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh)
  233. self._top_layers_job.cancel()
  234. self.setBusy(True)
  235. self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
  236. self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
  237. self._top_layers_job.start()
  238. def _updateCurrentLayerMesh(self, job):
  239. self.setBusy(False)
  240. if not job.getResult():
  241. return
  242. self.resetLayerData() # Reset the layer data only when job is done. Doing it now prevents "blinking" data.
  243. self._current_layer_mesh = job.getResult().get("layers")
  244. self._current_layer_jumps = job.getResult().get("jumps")
  245. self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
  246. self._top_layers_job = None
  247. def _onPreferencesChanged(self, preference):
  248. if preference != "view/top_layer_count" and preference != "view/only_show_top_layers":
  249. return
  250. self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count"))
  251. self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers"))
  252. self._startUpdateTopLayers()
  253. class _CreateTopLayersJob(Job):
  254. def __init__(self, scene, layer_number, solid_layers):
  255. super().__init__()
  256. self._scene = scene
  257. self._layer_number = layer_number
  258. self._solid_layers = solid_layers
  259. self._cancel = False
  260. def run(self):
  261. layer_data = None
  262. for node in DepthFirstIterator(self._scene.getRoot()):
  263. layer_data = node.callDecoration("getLayerData")
  264. if layer_data:
  265. break
  266. if self._cancel or not layer_data:
  267. return
  268. layer_mesh = MeshBuilder()
  269. for i in range(self._solid_layers):
  270. layer_number = self._layer_number - i
  271. if layer_number < 0:
  272. continue
  273. try:
  274. layer = layer_data.getLayer(layer_number).createMesh()
  275. except Exception:
  276. Logger.logException("w", "An exception occurred while creating layer mesh.")
  277. return
  278. if not layer or layer.getVertices() is None:
  279. continue
  280. layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices())
  281. layer_mesh.addVertices(layer.getVertices())
  282. # Scale layer color by a brightness factor based on the current layer number
  283. # This will result in a range of 0.5 - 1.0 to multiply colors by.
  284. brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0
  285. brightness[0, 3] = 1.0
  286. layer_mesh.addColors(layer.getColors() * brightness)
  287. if self._cancel:
  288. return
  289. Job.yieldThread()
  290. if self._cancel:
  291. return
  292. Job.yieldThread()
  293. jump_mesh = layer_data.getLayer(self._layer_number).createJumps()
  294. if not jump_mesh or jump_mesh.getVertices() is None:
  295. jump_mesh = None
  296. self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
  297. def cancel(self):
  298. self._cancel = True
  299. super().cancel()