diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py index f11df837b1..7443340c5b 100644 --- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py +++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py @@ -163,7 +163,7 @@ class ProcessSlicedLayersJob(Job): view = Application.getInstance().getController().getActiveView() if view.getPluginId() == "LayerView": - view.getLayerPass().resetLayerData() + view.resetLayerData() if self._progress: self._progress.hide() diff --git a/plugins/LayerView/LayerPass.py b/plugins/LayerView/LayerPass.py index 713709664f..a85ef5292d 100644 --- a/plugins/LayerView/LayerPass.py +++ b/plugins/LayerView/LayerPass.py @@ -1,118 +1,30 @@ # Copyright (c) 2016 Ultimaker B.V. # Cura is released under the terms of the AGPLv3 or higher. -from UM.Logger import Logger -from UM.Application import Application -from UM.Resources import Resources -from UM.Preferences import Preferences -from UM.Signal import Signal -from UM.Job import Job - from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator +from UM.Resources import Resources from UM.Scene.SceneNode import SceneNode from UM.Scene.ToolHandle import ToolHandle -from UM.Mesh.MeshBuilder import MeshBuilder +from UM.Application import Application + from UM.View.RenderPass import RenderPass from UM.View.RenderBatch import RenderBatch from UM.View.GL.OpenGL import OpenGL -import numpy - ## RenderPass used to display g-code paths. class LayerPass(RenderPass): def __init__(self, width, height): super().__init__("layerview", width, height) - self._controller = Application.getInstance().getController() - self._shader = None self._tool_handle_shader = None self._gl = OpenGL.getInstance().getBindingsObject() - self._scene = self._controller.getScene() + self._scene = Application.getInstance().getController().getScene() - self._scene.getRoot().childrenChanged.connect(self._onSceneChanged) - self._max_layers = 0 - self._current_layer_num = 0 - self._current_layer_mesh = None - self._current_layer_jumps = None - self._top_layers_job = None - self._activity = False - self._old_max_layers = 0 + self._layer_view = None - self._busy = False - - Preferences.getInstance().addPreference("view/top_layer_count", 5) - Preferences.getInstance().addPreference("view/only_show_top_layers", False) - Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) - - self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) - self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers")) - - busyChanged = Signal() - - def isBusy(self): - return self._busy - - def setBusy(self, busy): - if busy != self._busy: - self._busy = busy - self.busyChanged.emit() - - def getActivity(self): - return self._activity - - def getCurrentLayer(self): - return self._current_layer_num - - def getMaxLayers(self): - return self._max_layers - - def resetLayerData(self): - self._current_layer_mesh = None - self._current_layer_jumps = None - - def setLayer(self, value): - if self._current_layer_num != value: - self._current_layer_num = value - if self._current_layer_num < 0: - self._current_layer_num = 0 - if self._current_layer_num > self._max_layers: - self._current_layer_num = self._max_layers - - self._startUpdateTopLayers() - - self.currentLayerNumChanged.emit() - - def calculateMaxLayers(self): - self._activity = True - - self._old_max_layers = self._max_layers - ## Recalculate num max layers - new_max_layers = 0 - for node in DepthFirstIterator(self._scene.getRoot()): - layer_data = node.callDecoration("getLayerData") - if not layer_data: - continue - - if new_max_layers < len(layer_data.getLayers()): - new_max_layers = len(layer_data.getLayers()) - 1 - - if new_max_layers > 0 and new_max_layers != self._old_max_layers: - self._max_layers = new_max_layers - - # The qt slider has a bit of weird behavior that the maxvalue needs to be changed first - # if it's the largest value. If we don't do this, we can have a slider block outside of the - # slider. - if new_max_layers > self._current_layer_num: - self.maxLayersChanged.emit() - self.setLayer(int(self._max_layers)) - else: - self.setLayer(int(self._max_layers)) - self.maxLayersChanged.emit() - self._startUpdateTopLayers() - - maxLayersChanged = Signal() - currentLayerNumChanged = Signal() + def setLayerView(self, layerview): + self._layerview = layerview def render(self): if not self._shader: @@ -134,12 +46,12 @@ class LayerPass(RenderPass): continue # Render all layers below a certain number as line mesh instead of vertices. - if self._current_layer_num - self._solid_layers > -1 and not self._only_show_top_layers: + if self._layerview._current_layer_num - self._layerview._solid_layers > -1 and not self._layerview._only_show_top_layers: start = 0 end = 0 element_counts = layer_data.getElementCounts() for layer, counts in element_counts.items(): - if layer + self._solid_layers > self._current_layer_num: + if layer + self._layerview._solid_layers > self._layerview._current_layer_num: break end += counts @@ -151,11 +63,11 @@ class LayerPass(RenderPass): # Create a new batch that is not range-limited batch = RenderBatch(self._shader, type = RenderBatch.RenderType.Solid) - if self._current_layer_mesh: - batch.addItem(node.getWorldTransformation(), self._current_layer_mesh) + if self._layerview._current_layer_mesh: + batch.addItem(node.getWorldTransformation(), self._layerview._current_layer_mesh) - if self._current_layer_jumps: - batch.addItem(node.getWorldTransformation(), self._current_layer_jumps) + if self._layerview._current_layer_jumps: + batch.addItem(node.getWorldTransformation(), self._layerview._current_layer_jumps) if len(batch.items) > 0: batch.render(self._scene.getActiveCamera()) @@ -165,101 +77,3 @@ class LayerPass(RenderPass): tool_handle_batch.render(self._scene.getActiveCamera()) self.release() - - def _onSceneChanged(self, node): - self.calculateMaxLayers() - - def _startUpdateTopLayers(self): - if self._top_layers_job: - self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh) - self._top_layers_job.cancel() - - self.setBusy(True) - - self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers) - self._top_layers_job.finished.connect(self._updateCurrentLayerMesh) - self._top_layers_job.start() - - def _updateCurrentLayerMesh(self, job): - self.setBusy(False) - - if not job.getResult(): - return - self.resetLayerData() # Reset the layer data only when job is done. Doing it now prevents "blinking" data. - self._current_layer_mesh = job.getResult().get("layers") - self._current_layer_jumps = job.getResult().get("jumps") - self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot()) - - self._top_layers_job = None - - def _onPreferencesChanged(self, preference): - if preference != "view/top_layer_count" and preference != "view/only_show_top_layers": - return - - self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) - self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers")) - - self._startUpdateTopLayers() - - -class _CreateTopLayersJob(Job): - def __init__(self, scene, layer_number, solid_layers): - super().__init__() - - self._scene = scene - self._layer_number = layer_number - self._solid_layers = solid_layers - self._cancel = False - - def run(self): - layer_data = None - for node in DepthFirstIterator(self._scene.getRoot()): - layer_data = node.callDecoration("getLayerData") - if layer_data: - break - - if self._cancel or not layer_data: - return - - layer_mesh = MeshBuilder() - for i in range(self._solid_layers): - layer_number = self._layer_number - i - if layer_number < 0: - continue - - try: - layer = layer_data.getLayer(layer_number).createMesh() - except Exception: - Logger.logException("w", "An exception occurred while creating layer mesh.") - return - - if not layer or layer.getVertices() is None: - continue - - layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices()) - layer_mesh.addVertices(layer.getVertices()) - - # Scale layer color by a brightness factor based on the current layer number - # This will result in a range of 0.5 - 1.0 to multiply colors by. - brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0 - brightness[0, 3] = 1.0 - layer_mesh.addColors(layer.getColors() * brightness) - - if self._cancel: - return - - Job.yieldThread() - - if self._cancel: - return - - Job.yieldThread() - jump_mesh = layer_data.getLayer(self._layer_number).createJumps() - if not jump_mesh or jump_mesh.getVertices() is None: - jump_mesh = None - - self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh}) - - def cancel(self): - self._cancel = True - super().cancel() diff --git a/plugins/LayerView/LayerView.py b/plugins/LayerView/LayerView.py index 33476201fd..5e30e2ad90 100644 --- a/plugins/LayerView/LayerView.py +++ b/plugins/LayerView/LayerView.py @@ -6,16 +6,22 @@ from UM.View.View import View from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Resources import Resources from UM.Event import Event, KeyEvent +from UM.Signal import Signal from UM.Scene.Selection import Selection from UM.Math.Color import Color +from UM.Mesh.MeshBuilder import MeshBuilder +from UM.Job import Job +from UM.Preferences import Preferences +from UM.Logger import Logger from UM.Scene.SceneNode import SceneNode +from UM.View.RenderBatch import RenderBatch from UM.View.GL.OpenGL import OpenGL from UM.Message import Message from UM.Application import Application from cura.ConvexHullNode import ConvexHullNode -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QTimer from PyQt5.QtWidgets import QApplication from . import LayerViewProxy @@ -25,14 +31,23 @@ catalog = i18nCatalog("cura") from . import LayerPass +import numpy import os.path ## View used to display g-code paths. class LayerView(View): def __init__(self): super().__init__() - self._proxy = LayerViewProxy.LayerViewProxy() - self._global_container_stack = None + + self._max_layers = 0 + self._current_layer_num = 0 + self._current_layer_mesh = None + self._current_layer_jumps = None + self._top_layers_job = None + self._activity = False + self._old_max_layers = 0 + + self._busy = False self._ghost_shader = None self._layer_pass = None @@ -41,24 +56,55 @@ class LayerView(View): self._layerview_composite_shader = None self._old_composite_shader = None + self._global_container_stack = None + self._proxy = LayerViewProxy.LayerViewProxy() + self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) + + Preferences.getInstance().addPreference("view/top_layer_count", 5) + Preferences.getInstance().addPreference("view/only_show_top_layers", False) + Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) + + self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) + self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers")) self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled")) + def getActivity(self): + return self._activity def getLayerPass(self): if not self._layer_pass: # Currently the RenderPass constructor requires a size > 0 # This should be fixed in RenderPass's constructor. self._layer_pass = LayerPass.LayerPass(1, 1) + self._layer_pass.setLayerView(self) self.getRenderer().addRenderPass(self._layer_pass) return self._layer_pass + def getCurrentLayer(self): + return self._current_layer_num + + def _onSceneChanged(self, node): + self.calculateMaxLayers() + + def getMaxLayers(self): + return self._max_layers + + busyChanged = Signal() + def getActivity(self): return self._activity - ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created - # as this caused some issues. - def getProxy(self, engine, script_engine): - return self._proxy + def isBusy(self): + return self._busy + + def setBusy(self, busy): + if busy != self._busy: + self._busy = busy + self.busyChanged.emit() + + def resetLayerData(self): + self._current_layer_mesh = None + self._current_layer_jumps = None def beginRendering(self): scene = self.getController().getScene() @@ -78,6 +124,55 @@ class LayerView(View): if node.getMeshData() and node.isVisible(): renderer.queueNode(node, transparent = True, shader = self._ghost_shader) + def setLayer(self, value): + if self._current_layer_num != value: + self._current_layer_num = value + if self._current_layer_num < 0: + self._current_layer_num = 0 + if self._current_layer_num > self._max_layers: + self._current_layer_num = self._max_layers + + self._startUpdateTopLayers() + + self.currentLayerNumChanged.emit() + + def calculateMaxLayers(self): + scene = self.getController().getScene() + self._activity = True + + self._old_max_layers = self._max_layers + ## Recalculate num max layers + new_max_layers = 0 + for node in DepthFirstIterator(scene.getRoot()): + layer_data = node.callDecoration("getLayerData") + if not layer_data: + continue + + if new_max_layers < len(layer_data.getLayers()): + new_max_layers = len(layer_data.getLayers()) - 1 + + if new_max_layers > 0 and new_max_layers != self._old_max_layers: + self._max_layers = new_max_layers + + # The qt slider has a bit of weird behavior that if the maxvalue needs to be changed first + # if it's the largest value. If we don't do this, we can have a slider block outside of the + # slider. + if new_max_layers > self._current_layer_num: + self.maxLayersChanged.emit() + self.setLayer(int(self._max_layers)) + else: + self.setLayer(int(self._max_layers)) + self.maxLayersChanged.emit() + self._startUpdateTopLayers() + + maxLayersChanged = Signal() + currentLayerNumChanged = Signal() + + ## Hackish way to ensure the proxy is already created, which ensures that the layerview.qml is already created + # as this caused some issues. + def getProxy(self, engine, script_engine): + return self._proxy + def endRendering(self): pass @@ -86,13 +181,16 @@ class LayerView(View): ctrl_is_active = modifiers == Qt.ControlModifier if event.type == Event.KeyPressEvent and ctrl_is_active: if event.key == KeyEvent.UpKey: - self.getLayerPass().setLayer(self._current_layer_num + 1) + self.setLayer(self._current_layer_num + 1) return True if event.key == KeyEvent.DownKey: - self.getLayerPass().setLayer(self._current_layer_num - 1) + self.setLayer(self._current_layer_num - 1) return True if event.type == Event.ViewActivateEvent: + # Make sure the LayerPass is created + self.getLayerPass() + Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged) self._onGlobalStackChanged() @@ -119,7 +217,6 @@ class LayerView(View): self._composite_pass.setLayerBindings(self._old_layer_bindings) self._composite_pass.setCompositeShader(self._old_composite_shader) - def _onGlobalStackChanged(self): if self._global_container_stack: self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged) @@ -136,3 +233,100 @@ class LayerView(View): self._wireprint_warning_message.show() else: self._wireprint_warning_message.hide() + + + def _startUpdateTopLayers(self): + if self._top_layers_job: + self._top_layers_job.finished.disconnect(self._updateCurrentLayerMesh) + self._top_layers_job.cancel() + + self.setBusy(True) + + self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers) + self._top_layers_job.finished.connect(self._updateCurrentLayerMesh) + self._top_layers_job.start() + + def _updateCurrentLayerMesh(self, job): + self.setBusy(False) + + if not job.getResult(): + return + self.resetLayerData() # Reset the layer data only when job is done. Doing it now prevents "blinking" data. + self._current_layer_mesh = job.getResult().get("layers") + self._current_layer_jumps = job.getResult().get("jumps") + self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot()) + + self._top_layers_job = None + + def _onPreferencesChanged(self, preference): + if preference != "view/top_layer_count" and preference != "view/only_show_top_layers": + return + + self._solid_layers = int(Preferences.getInstance().getValue("view/top_layer_count")) + self._only_show_top_layers = bool(Preferences.getInstance().getValue("view/only_show_top_layers")) + + self._startUpdateTopLayers() + + +class _CreateTopLayersJob(Job): + def __init__(self, scene, layer_number, solid_layers): + super().__init__() + + self._scene = scene + self._layer_number = layer_number + self._solid_layers = solid_layers + self._cancel = False + + def run(self): + layer_data = None + for node in DepthFirstIterator(self._scene.getRoot()): + layer_data = node.callDecoration("getLayerData") + if layer_data: + break + + if self._cancel or not layer_data: + return + + layer_mesh = MeshBuilder() + for i in range(self._solid_layers): + layer_number = self._layer_number - i + if layer_number < 0: + continue + + try: + layer = layer_data.getLayer(layer_number).createMesh() + except Exception: + Logger.logException("w", "An exception occurred while creating layer mesh.") + return + + if not layer or layer.getVertices() is None: + continue + + layer_mesh.addIndices(layer_mesh.getVertexCount() + layer.getIndices()) + layer_mesh.addVertices(layer.getVertices()) + + # Scale layer color by a brightness factor based on the current layer number + # This will result in a range of 0.5 - 1.0 to multiply colors by. + brightness = numpy.ones((1, 4), dtype=numpy.float32) * (2.0 - (i / self._solid_layers)) / 2.0 + brightness[0, 3] = 1.0 + layer_mesh.addColors(layer.getColors() * brightness) + + if self._cancel: + return + + Job.yieldThread() + + if self._cancel: + return + + Job.yieldThread() + jump_mesh = layer_data.getLayer(self._layer_number).createJumps() + if not jump_mesh or jump_mesh.getVertices() is None: + jump_mesh = None + + self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh}) + + def cancel(self): + self._cancel = True + super().cancel() + diff --git a/plugins/LayerView/LayerViewProxy.py b/plugins/LayerView/LayerViewProxy.py index 32f1a27e4e..bb9554ebf1 100644 --- a/plugins/LayerView/LayerViewProxy.py +++ b/plugins/LayerView/LayerViewProxy.py @@ -19,27 +19,27 @@ class LayerViewProxy(QObject): def getLayerActivity(self): active_view = self._controller.getActiveView() if type(active_view) == LayerView.LayerView.LayerView: - return active_view.getLayerPass().getActivity() + return active_view.getActivity() @pyqtProperty(int, notify = maxLayersChanged) def numLayers(self): active_view = self._controller.getActiveView() if type(active_view) == LayerView.LayerView.LayerView: - return active_view.getLayerPass().getMaxLayers() + return active_view.getMaxLayers() #return 100 @pyqtProperty(int, notify = currentLayerChanged) def currentLayer(self): active_view = self._controller.getActiveView() if type(active_view) == LayerView.LayerView.LayerView: - return active_view.getLayerPass().getCurrentLayer() + return active_view.getCurrentLayer() busyChanged = pyqtSignal() @pyqtProperty(bool, notify = busyChanged) def busy(self): active_view = self._controller.getActiveView() if type(active_view) == LayerView.LayerView.LayerView: - return active_view.getLayerPass().isBusy() + return active_view.isBusy() return False @@ -47,7 +47,7 @@ class LayerViewProxy(QObject): def setCurrentLayer(self, layer_num): active_view = self._controller.getActiveView() if type(active_view) == LayerView.LayerView.LayerView: - active_view.getLayerPass().setLayer(layer_num) + active_view.setLayer(layer_num) def _layerActivityChanged(self): self.activityChanged.emit() @@ -65,6 +65,6 @@ class LayerViewProxy(QObject): def _onActiveViewChanged(self): active_view = self._controller.getActiveView() if type(active_view) == LayerView.LayerView.LayerView: - active_view.getLayerPass().currentLayerNumChanged.connect(self._onLayerChanged) - active_view.getLayerPass().maxLayersChanged.connect(self._onMaxLayersChanged) - active_view.getLayerPass().busyChanged.connect(self._onBusyChanged) + active_view.currentLayerNumChanged.connect(self._onLayerChanged) + active_view.maxLayersChanged.connect(self._onMaxLayersChanged) + active_view.busyChanged.connect(self._onBusyChanged)