diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index e307df3f1e..415e49f71c 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -30,6 +30,7 @@ from UM.View.GL.ShaderProgram import ShaderProgram from UM.i18n import i18nCatalog from cura.CuraView import CuraView +from cura.LayerPolygon import LayerPolygon # To distinguish line types. from cura.Scene.ConvexHullNode import ConvexHullNode from cura.CuraApplication import CuraApplication @@ -115,6 +116,7 @@ class SimulationView(CuraView): Application.getInstance().getPreferences().addPreference("layerview/show_infill", True) Application.getInstance().getPreferences().addPreference("layerview/show_starts", True) + self.visibleStructuresChanged.connect(self.calculateColorSchemeLimits) self._updateWithPreferences() self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count")) @@ -198,6 +200,7 @@ class SimulationView(CuraView): if node.getMeshData() is None: return self.setActivity(False) + self.calculateColorSchemeLimits() self.calculateMaxLayers() self.calculateMaxPathsOnLayer(self._current_layer_num) @@ -218,12 +221,6 @@ class SimulationView(CuraView): def resetLayerData(self) -> None: self._current_layer_mesh = None self._current_layer_jumps = None - self._max_feedrate = sys.float_info.min - self._min_feedrate = sys.float_info.max - self._max_thickness = sys.float_info.min - self._min_thickness = sys.float_info.max - self._max_line_width = sys.float_info.min - self._min_line_width = sys.float_info.max def beginRendering(self) -> None: scene = self.getController().getScene() @@ -334,37 +331,52 @@ class SimulationView(CuraView): # If more than 16 extruders are called for, this should be converted to a sampler1d. return Matrix(self._extruder_opacity) - def setShowTravelMoves(self, show): + def setShowTravelMoves(self, show: bool) -> None: + if show == self._show_travel_moves: + return self._show_travel_moves = show self.currentLayerNumChanged.emit() + self.visibleStructuresChanged.emit() - def getShowTravelMoves(self): + def getShowTravelMoves(self) -> bool: return self._show_travel_moves def setShowHelpers(self, show: bool) -> None: + if show == self._show_helpers: + return self._show_helpers = show self.currentLayerNumChanged.emit() + self.visibleStructuresChanged.emit() def getShowHelpers(self) -> bool: return self._show_helpers def setShowSkin(self, show: bool) -> None: + if show == self._show_skin: + return self._show_skin = show self.currentLayerNumChanged.emit() + self.visibleStructuresChanged.emit() def getShowSkin(self) -> bool: return self._show_skin def setShowInfill(self, show: bool) -> None: + if show == self._show_infill: + return self._show_infill = show self.currentLayerNumChanged.emit() + self.visibleStructuresChanged.emit() def getShowInfill(self) -> bool: return self._show_infill def setShowStarts(self, show: bool) -> None: + if show == self._show_starts: + return self._show_starts = show self.currentLayerNumChanged.emit() + self.visibleStructuresChanged.emit() def getShowStarts(self) -> bool: return self._show_starts @@ -400,11 +412,14 @@ class SimulationView(CuraView): return self._min_line_width def calculateMaxLayers(self) -> None: + """ + Calculates number of layers, triggers signals if the number of layers changed and makes sure the top layers are + recalculated for legacy layer view. + """ scene = self.getController().getScene() self._old_max_layers = self._max_layers new_max_layers = -1 - """Recalculate num max layers""" for node in DepthFirstIterator(scene.getRoot()): # type: ignore layer_data = node.callDecoration("getLayerData") if not layer_data: @@ -419,19 +434,6 @@ class SimulationView(CuraView): if len(layer_data.getLayer(layer_id).polygons) < 1: continue - # Store the max and min feedrates and thicknesses for display purposes - for p in layer_data.getLayer(layer_id).polygons: - self._max_feedrate = max(float(p.lineFeedrates.max()), self._max_feedrate) - self._min_feedrate = min(float(p.lineFeedrates.min()), self._min_feedrate) - self._max_line_width = max(float(p.lineWidths.max()), self._max_line_width) - self._min_line_width = min(float(p.lineWidths.min()), self._min_line_width) - self._max_thickness = max(float(p.lineThicknesses.max()), self._max_thickness) - try: - self._min_thickness = min(float(p.lineThicknesses[numpy.nonzero(p.lineThicknesses)].min()), self._min_thickness) - except ValueError: - # Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding - # the zero) can't be calculated - Logger.log("i", "Min thickness can't be calculated because all the values are zero") if max_layer_number < layer_id: max_layer_number = layer_id if min_layer_number > layer_id: @@ -455,6 +457,73 @@ class SimulationView(CuraView): self.maxLayersChanged.emit() self._startUpdateTopLayers() + def calculateColorSchemeLimits(self) -> None: + """ + Calculates the limits of the colour schemes, depending on the layer view data that is visible to the user. + """ + # Before we start, save the old values so that we can tell if any of the spectrums need to change. + old_min_feedrate = self._min_feedrate + old_max_feedrate = self._max_feedrate + old_min_linewidth = self._min_line_width + old_max_linewidth = self._max_line_width + old_min_thickness = self._min_thickness + old_max_thickness = self._max_thickness + + self._min_feedrate = sys.float_info.max + self._max_feedrate = sys.float_info.min + self._min_line_width = sys.float_info.max + self._max_line_width = sys.float_info.min + self._min_thickness = sys.float_info.max + self._max_thickness = sys.float_info.min + + # The colour scheme is only influenced by the visible lines, so filter the lines by if they should be visible. + visible_line_types = [] + if self.getShowSkin(): # Actually "shell". + visible_line_types.append(LayerPolygon.SkinType) + visible_line_types.append(LayerPolygon.Inset0Type) + visible_line_types.append(LayerPolygon.InsetXType) + if self.getShowInfill(): + visible_line_types.append(LayerPolygon.InfillType) + if self.getShowHelpers(): + visible_line_types.append(LayerPolygon.PrimeTowerType) + visible_line_types.append(LayerPolygon.SkirtType) + visible_line_types.append(LayerPolygon.SupportType) + visible_line_types.append(LayerPolygon.SupportInfillType) + visible_line_types.append(LayerPolygon.SupportInterfaceType) + if self.getShowTravelMoves(): + visible_line_types.append(LayerPolygon.MoveCombingType) + visible_line_types.append(LayerPolygon.MoveRetractionType) + + for node in DepthFirstIterator(self.getController().getScene().getRoot()): + layer_data = node.callDecoration("getLayerData") + if not layer_data: + continue + + for layer_index in layer_data.getLayers(): + for polyline in layer_data.getLayer(layer_index).polygons: + is_visible = numpy.isin(polyline.types, visible_line_types) + visible_indices = numpy.where(is_visible)[0] + if visible_indices.size == 0: # No items to take maximum or minimum of. + continue + visible_feedrates = numpy.take(polyline.lineFeedrates, visible_indices) + visible_linewidths = numpy.take(polyline.lineWidths, visible_indices) + visible_thicknesses = numpy.take(polyline.lineThicknesses, visible_indices) + self._max_feedrate = max(float(visible_feedrates.max()), self._max_feedrate) + self._min_feedrate = min(float(visible_feedrates.min()), self._min_feedrate) + self._max_line_width = max(float(visible_linewidths.max()), self._max_line_width) + self._min_line_width = min(float(visible_linewidths.min()), self._min_line_width) + self._max_thickness = max(float(visible_thicknesses.max()), self._max_thickness) + try: + self._min_thickness = min(float(visible_thicknesses[numpy.nonzero(visible_thicknesses)].min()), self._min_thickness) + except ValueError: + # Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding the zero) can't be calculated. + Logger.log("i", "Min thickness can't be calculated because all the values are zero") + + if old_min_feedrate != self._min_feedrate or old_max_feedrate != self._max_feedrate \ + or old_min_linewidth != self._min_line_width or old_max_linewidth != self._max_line_width \ + or old_min_thickness != self._min_thickness or old_max_thickness != self._max_thickness: + self.colorSchemeLimitsChanged.emit() + def calculateMaxPathsOnLayer(self, layer_num: int) -> None: # Update the currentPath scene = self.getController().getScene() @@ -481,6 +550,8 @@ class SimulationView(CuraView): preferencesChanged = Signal() busyChanged = Signal() activityChanged = Signal() + visibleStructuresChanged = Signal() + colorSchemeLimitsChanged = Signal() def getProxy(self, engine, script_engine): """Hackish way to ensure the proxy is already created @@ -512,6 +583,7 @@ class SimulationView(CuraView): Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged) self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged) + self.calculateColorSchemeLimits() self.calculateMaxLayers() self.calculateMaxPathsOnLayer(self._current_layer_num) diff --git a/plugins/SimulationView/SimulationViewMenuComponent.qml b/plugins/SimulationView/SimulationViewMenuComponent.qml index 7c8e086909..d7552a4c30 100644 --- a/plugins/SimulationView/SimulationViewMenuComponent.qml +++ b/plugins/SimulationView/SimulationViewMenuComponent.qml @@ -389,17 +389,17 @@ Cura.ExpandableComponent // Feedrate selected if (UM.Preferences.getValue("layerview/layer_view_type") == 2) { - return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2) + return parseFloat(UM.SimulationView.minFeedrate).toFixed(2) } // Layer thickness selected if (UM.Preferences.getValue("layerview/layer_view_type") == 3) { - return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2) + return parseFloat(UM.SimulationView.minThickness).toFixed(2) } //Line width selected if(UM.Preferences.getValue("layerview/layer_view_type") == 4) { - return parseFloat(UM.SimulationView.getMinLineWidth()).toFixed(2); + return parseFloat(UM.SimulationView.minLineWidth).toFixed(2); } } return catalog.i18nc("@label","min") @@ -448,17 +448,17 @@ Cura.ExpandableComponent // Feedrate selected if (UM.Preferences.getValue("layerview/layer_view_type") == 2) { - return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2) + return parseFloat(UM.SimulationView.maxFeedrate).toFixed(2) } // Layer thickness selected if (UM.Preferences.getValue("layerview/layer_view_type") == 3) { - return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2) + return parseFloat(UM.SimulationView.maxThickness).toFixed(2) } //Line width selected if(UM.Preferences.getValue("layerview/layer_view_type") == 4) { - return parseFloat(UM.SimulationView.getMaxLineWidth()).toFixed(2); + return parseFloat(UM.SimulationView.maxLineWidth).toFixed(2); } } return catalog.i18nc("@label","max") diff --git a/plugins/SimulationView/SimulationViewProxy.py b/plugins/SimulationView/SimulationViewProxy.py index 12947f6464..bdf787ab3a 100644 --- a/plugins/SimulationView/SimulationViewProxy.py +++ b/plugins/SimulationView/SimulationViewProxy.py @@ -1,4 +1,4 @@ -# Copyright (c) 2018 Ultimaker B.V. +# Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. from typing import TYPE_CHECKING @@ -28,6 +28,7 @@ class SimulationViewProxy(QObject): globalStackChanged = pyqtSignal() preferencesChanged = pyqtSignal() busyChanged = pyqtSignal() + colorSchemeLimitsChanged = pyqtSignal() @pyqtProperty(bool, notify=activityChanged) def layerActivity(self): @@ -101,28 +102,28 @@ class SimulationViewProxy(QObject): def getSimulationRunning(self): return self._simulation_view.isSimulationRunning() - @pyqtSlot(result=float) - def getMinFeedrate(self): + @pyqtProperty(float, notify = colorSchemeLimitsChanged) + def minFeedrate(self): return self._simulation_view.getMinFeedrate() - @pyqtSlot(result=float) - def getMaxFeedrate(self): + @pyqtProperty(float, notify = colorSchemeLimitsChanged) + def maxFeedrate(self): return self._simulation_view.getMaxFeedrate() - @pyqtSlot(result=float) - def getMinThickness(self): + @pyqtProperty(float, notify = colorSchemeLimitsChanged) + def minThickness(self): return self._simulation_view.getMinThickness() - @pyqtSlot(result=float) - def getMaxThickness(self): + @pyqtProperty(float, notify = colorSchemeLimitsChanged) + def maxThickness(self): return self._simulation_view.getMaxThickness() - @pyqtSlot(result=float) - def getMaxLineWidth(self): + @pyqtProperty(float, notify = colorSchemeLimitsChanged) + def maxLineWidth(self): return self._simulation_view.getMaxLineWidth() - @pyqtSlot(result=float) - def getMinLineWidth(self): + @pyqtProperty(float, notify = colorSchemeLimitsChanged) + def minLineWidth(self): return self._simulation_view.getMinLineWidth() # Opacity 0..1 @@ -153,6 +154,9 @@ class SimulationViewProxy(QObject): self.currentLayerChanged.emit() self._layerActivityChanged() + def _onColorSchemeLimitsChanged(self): + self.colorSchemeLimitsChanged.emit() + def _onPathChanged(self): self.currentPathChanged.emit() self._layerActivityChanged() @@ -182,6 +186,7 @@ class SimulationViewProxy(QObject): active_view = self._controller.getActiveView() if active_view == self._simulation_view: self._simulation_view.currentLayerNumChanged.connect(self._onLayerChanged) + self._simulation_view.colorSchemeLimitsChanged.connect(self._onColorSchemeLimitsChanged) self._simulation_view.currentPathNumChanged.connect(self._onPathChanged) self._simulation_view.maxLayersChanged.connect(self._onMaxLayersChanged) self._simulation_view.maxPathsChanged.connect(self._onMaxPathsChanged) @@ -194,6 +199,7 @@ class SimulationViewProxy(QObject): # Disconnect all of em again. self.is_simulationView_selected = False self._simulation_view.currentLayerNumChanged.disconnect(self._onLayerChanged) + self._simulation_view.colorSchemeLimitsChanged.connect(self._onColorSchemeLimitsChanged) self._simulation_view.currentPathNumChanged.disconnect(self._onPathChanged) self._simulation_view.maxLayersChanged.disconnect(self._onMaxLayersChanged) self._simulation_view.maxPathsChanged.disconnect(self._onMaxPathsChanged) diff --git a/plugins/SimulationView/layers3d.shader b/plugins/SimulationView/layers3d.shader index c7cf7f061c..a3178997d7 100644 --- a/plugins/SimulationView/layers3d.shader +++ b/plugins/SimulationView/layers3d.shader @@ -44,7 +44,15 @@ vertex41core = vec4 feedrateGradientColor(float abs_value, float min_value, float max_value) { - float value = (abs_value - min_value)/(max_value - min_value); + float value; + if(abs(max_value - min_value) < 0.0001) //Max and min are equal (barring floating point rounding errors). + { + value = 0.5; //Pick a colour in exactly the middle of the range. + } + else + { + value = (abs_value - min_value) / (max_value - min_value); + } float red = value; float green = 1-abs(1-4*value); if (value > 0.375) @@ -57,7 +65,15 @@ vertex41core = vec4 layerThicknessGradientColor(float abs_value, float min_value, float max_value) { - float value = (abs_value - min_value)/(max_value - min_value); + float value; + if(abs(max_value - min_value) < 0.0001) //Max and min are equal (barring floating point rounding errors). + { + value = 0.5; //Pick a colour in exactly the middle of the range. + } + else + { + value = (abs_value - min_value) / (max_value - min_value); + } float red = min(max(4*value-2, 0), 1); float green = min(1.5*value, 0.75); if (value > 0.75) @@ -70,7 +86,15 @@ vertex41core = vec4 lineWidthGradientColor(float abs_value, float min_value, float max_value) { - float value = (abs_value - min_value) / (max_value - min_value); + float value; + if(abs(max_value - min_value) < 0.0001) //Max and min are equal (barring floating point rounding errors). + { + value = 0.5; //Pick a colour in exactly the middle of the range. + } + else + { + value = (abs_value - min_value) / (max_value - min_value); + } float red = value; float green = 1 - abs(1 - 4 * value); if(value > 0.375)