From cfec5e0cc112181d4db4af13ed6a0f8ec3047669 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Tue, 19 Dec 2023 10:12:56 +0100 Subject: [PATCH 1/7] Proof of concept for simulation Co-authored-by: Casper Lamboo CURA-7647 --- cura/LayerPolygon.py | 6 +- plugins/SimulationView/SimulationPass.py | 32 +++-- plugins/SimulationView/SimulationView.py | 124 +++++++++++++----- .../SimulationViewMainComponent.qml | 47 +------ plugins/SimulationView/SimulationViewProxy.py | 23 +--- 5 files changed, 128 insertions(+), 104 deletions(-) diff --git a/cura/LayerPolygon.py b/cura/LayerPolygon.py index e5fd307dc9..e772a8b78e 100644 --- a/cura/LayerPolygon.py +++ b/cura/LayerPolygon.py @@ -67,7 +67,7 @@ class LayerPolygon: # Buffering the colors shouldn't be necessary as it is not # re-used and can save a lot of memory usage. self._color_map = LayerPolygon.getColorMap() - self._colors = self._color_map[self._types] # type: numpy.ndarray + self._colors: numpy.ndarray = self._color_map[self._types] # When type is used as index returns true if type == LayerPolygon.InfillType # or type == LayerPolygon.SkinType @@ -75,8 +75,8 @@ class LayerPolygon: # Should be generated in better way, not hardcoded. self._is_infill_or_skin_type_map = numpy.array([0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0], dtype=bool) - self._build_cache_line_mesh_mask = None # type: Optional[numpy.ndarray] - self._build_cache_needed_points = None # type: Optional[numpy.ndarray] + self._build_cache_line_mesh_mask: Optional[numpy.ndarray] = None + self._build_cache_needed_points: Optional[numpy.ndarray] = None def buildCache(self) -> None: # For the line mesh we do not draw Infill or Jumps. Therefore those lines are filtered out. diff --git a/plugins/SimulationView/SimulationPass.py b/plugins/SimulationView/SimulationPass.py index 1294b37db4..3294f4b1e6 100644 --- a/plugins/SimulationView/SimulationPass.py +++ b/plugins/SimulationView/SimulationPass.py @@ -35,7 +35,7 @@ class SimulationPass(RenderPass): self._nozzle_shader = None self._disabled_shader = None self._old_current_layer = 0 - self._old_current_path = 0 + self._old_current_path: float = 0.0 self._switching_layers = True # Tracking whether the user is moving across layers (True) or across paths (False). If false, lower layers render as shadowy. self._gl = OpenGL.getInstance().getBindingsObject() self._scene = Application.getInstance().getController().getScene() @@ -139,7 +139,7 @@ class SimulationPass(RenderPass): continue # Render all layers below a certain number as line mesh instead of vertices. - if self._layer_view._current_layer_num > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())): + if self._layer_view.getCurrentLayer() > -1 and ((not self._layer_view._only_show_top_layers) or (not self._layer_view.getCompatibilityMode())): start = 0 end = 0 element_counts = layer_data.getElementCounts() @@ -147,7 +147,7 @@ class SimulationPass(RenderPass): # In the current layer, we show just the indicated paths if layer == self._layer_view._current_layer_num: # We look for the position of the head, searching the point of the current path - index = self._layer_view._current_path_num + index = int(self._layer_view.getCurrentPath()) offset = 0 for polygon in layer_data.getLayer(layer).polygons: # The size indicates all values in the two-dimension array, and the second dimension is @@ -157,23 +157,33 @@ class SimulationPass(RenderPass): offset = 1 # This is to avoid the first point when there is more than one polygon, since has the same value as the last point in the previous polygon continue # The head position is calculated and translated - head_position = Vector(polygon.data[index+offset][0], polygon.data[index+offset][1], polygon.data[index+offset][2]) + node.getWorldPosition() + ratio = self._layer_view.getCurrentPath() - index + pos_a = Vector(polygon.data[index + offset][0], polygon.data[index + offset][1], + polygon.data[index + offset][2]) + if ratio > 0.0001: + pos_b = Vector(polygon.data[index + offset + 1][0], + polygon.data[index + offset + 1][1], + polygon.data[index + offset + 1][2]) + vec = pos_a * (1.0 - ratio) + pos_b * ratio + head_position = vec + node.getWorldPosition() + else: + head_position = pos_a + node.getWorldPosition() break break - if self._layer_view._minimum_layer_num > layer: + if self._layer_view.getMinimumLayer() > layer: start += element_counts[layer] end += element_counts[layer] # Calculate the range of paths in the last layer current_layer_start = end - current_layer_end = end + self._layer_view._current_path_num * 2 # Because each point is used twice + current_layer_end = end + int( self._layer_view.getCurrentPath()) * 2 # Because each point is used twice # This uses glDrawRangeElements internally to only draw a certain range of lines. # All the layers but the current selected layer are rendered first - if self._old_current_path != self._layer_view._current_path_num: + if self._old_current_path != self._layer_view.getCurrentPath(): self._current_shader = self._layer_shadow_shader self._switching_layers = False - if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view._current_layer_num: + if not self._layer_view.isSimulationRunning() and self._old_current_layer != self._layer_view.getCurrentLayer(): self._current_shader = self._layer_shader self._switching_layers = True @@ -193,8 +203,8 @@ class SimulationPass(RenderPass): current_layer_batch.addItem(node.getWorldTransformation(), layer_data) current_layer_batch.render(self._scene.getActiveCamera()) - self._old_current_layer = self._layer_view._current_layer_num - self._old_current_path = self._layer_view._current_path_num + self._old_current_layer = self._layer_view.getCurrentLayer() + self._old_current_path = self._layer_view.getCurrentPath() # Create a new batch that is not range-limited batch = RenderBatch(self._layer_shader, type = RenderBatch.RenderType.Solid) @@ -230,4 +240,4 @@ class SimulationPass(RenderPass): if changed_object.callDecoration("getLayerData"): # Any layer data has changed. self._switching_layers = True self._old_current_layer = 0 - self._old_current_path = 0 + self._old_current_path = 0.0 diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index a659a6de97..64ec0dfc1b 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -40,7 +40,7 @@ from .SimulationViewProxy import SimulationViewProxy import numpy import os.path -from typing import Optional, TYPE_CHECKING, List, cast +from typing import Optional, TYPE_CHECKING, List, Tuple, cast if TYPE_CHECKING: from UM.Scene.SceneNode import SceneNode @@ -74,21 +74,20 @@ class SimulationView(CuraView): self._old_max_layers = 0 self._max_paths = 0 - self._current_path_num = 0 + self._current_path_num: float = 0.0 + self._current_time = 0.0 self._minimum_path_num = 0 self.currentLayerNumChanged.connect(self._onCurrentLayerNumChanged) - self._current_feedrates = {} - self._lengths_of_polyline ={} self._busy = False self._simulation_running = False - self._ghost_shader = None # type: Optional["ShaderProgram"] - self._layer_pass = None # type: Optional[SimulationPass] - self._composite_pass = None # type: Optional[CompositePass] - self._old_layer_bindings = None # type: Optional[List[str]] - self._simulationview_composite_shader = None # type: Optional["ShaderProgram"] - self._old_composite_shader = None # type: Optional["ShaderProgram"] + self._ghost_shader: Optional["ShaderProgram"] = None + self._layer_pass: Optional[SimulationPass] = None + self._composite_pass: Optional[CompositePass] = None + self._old_layer_bindings: Optional[List[str]] = None + self._simulationview_composite_shader: Optional["ShaderProgram"] = None + self._old_composite_shader: Optional["ShaderProgram"] = None self._max_feedrate = sys.float_info.min self._min_feedrate = sys.float_info.max @@ -99,13 +98,13 @@ class SimulationView(CuraView): self._min_flow_rate = sys.float_info.max self._max_flow_rate = sys.float_info.min - self._global_container_stack = None # type: Optional[ContainerStack] + self._global_container_stack: Optional[ContainerStack] = None self._proxy = None self._resetSettings() self._legend_items = None self._show_travel_moves = False - self._nozzle_node = None # type: Optional[NozzleNode] + self._nozzle_node: Optional[NozzleNode] = None Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5) Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False) @@ -127,13 +126,12 @@ class SimulationView(CuraView): self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers")) self._compatibility_mode = self._evaluateCompatibilityMode() - self._slice_first_warning_message = Message(catalog.i18nc("@info:status", - "Nothing is shown because you need to slice first."), - title = catalog.i18nc("@info:title", "No layers to show"), - option_text = catalog.i18nc("@info:option_text", - "Do not show this message again"), - option_state = False, - message_type = Message.MessageType.WARNING) + self._slice_first_warning_message = Message(catalog.i18nc("@info:status", "Nothing is shown because you need to slice first."), + title=catalog.i18nc("@info:title", "No layers to show"), + option_text=catalog.i18nc("@info:option_text", + "Do not show this message again"), + option_state=False, + message_type=Message.MessageType.WARNING) self._slice_first_warning_message.optionToggled.connect(self._onDontAskMeAgain) CuraApplication.getInstance().getPreferences().addPreference(self._no_layers_warning_preference, True) @@ -189,9 +187,82 @@ class SimulationView(CuraView): def getMaxLayers(self) -> int: return self._max_layers - def getCurrentPath(self) -> int: + def getCurrentPath(self) -> float: return self._current_path_num + def setTime(self, time: float) -> None: + self._current_time = time + + left_i = 0 + right_i = self._max_paths - 1 + + total_duration, cumulative_line_duration = self.cumulativeLineDuration() + + # make an educated guess about where to start + i = int(right_i * max(0.0, min(1.0, self._current_time / total_duration))) + + # binary search for the correct path + while left_i < right_i: + if cumulative_line_duration[i] <= self._current_time: + left_i = i + 1 + else: + right_i = i + i = int((left_i + right_i) / 2) + + left_value = cumulative_line_duration[i - 1] if i > 0 else 0.0 + right_value = cumulative_line_duration[i] + + assert (left_value <= self._current_time <= right_value) + + fractional_value = (self._current_time - left_value) / (right_value - left_value) + + self.setPath(i + fractional_value) + + def advanceTime(self, time_increase: float) -> bool: + """ + Advance the time by the given amount. + + :param time_increase: The amount of time to advance (in seconds). + :return: True if the time was advanced, False if the end of the simulation was reached. + """ + total_duration, cumulative_line_duration = self.cumulativeLineDuration() + + # time ratio + time_increase = time_increase + + if self._current_time + time_increase > total_duration: + # If we have reached the end of the simulation, go to the next layer. + if self.getCurrentLayer() == self.getMaxLayers(): + # If we are already at the last layer, go to the first layer. + self.setTime(total_duration) + return False + + # advance to the next layer, and reset the time + self.setLayer(self.getCurrentLayer() + 1) + self.setTime(0.0) + else: + self.setTime(self._current_time + time_increase) + return True + + def cumulativeLineDuration(self) -> Tuple[float, List[float]]: + # TODO: cache the total duration and cumulative line duration at each layer change event + cumulative_line_duration = [] + total_duration = 0.0 + for polyline in self.getLayerData().polygons: + for line_duration in list((polyline.lineLengths / polyline.lineFeedrates)[0]): + total_duration += line_duration + cumulative_line_duration.append(total_duration) + return total_duration, cumulative_line_duration + + def getLayerData(self) -> Optional["LayerData"]: + scene = self.getController().getScene() + for node in DepthFirstIterator(scene.getRoot()): # type: ignore + layer_data = node.callDecoration("getLayerData") + if not layer_data: + continue + return layer_data.getLayer(self.getCurrentLayer()) + return None + def getMinimumPath(self) -> int: return self._minimum_path_num @@ -279,7 +350,7 @@ class SimulationView(CuraView): self._startUpdateTopLayers() self.currentLayerNumChanged.emit() - def setPath(self, value: int) -> None: + def setPath(self, value: float) -> None: """ Set the upper end of the range of visible paths on the current layer. @@ -402,15 +473,6 @@ class SimulationView(CuraView): def getMaxFeedrate(self) -> float: return self._max_feedrate - def getSimulationTime(self, currentIndex) -> float: - try: - return (self._lengths_of_polyline[self._current_layer_num][currentIndex] / self._current_feedrates[self._current_layer_num][currentIndex])[0] - - except: - # In case of change in layers, currentIndex comes one more than the items in the lengths_of_polyline - # We give 1 second time for layer change - return 1.0 - def getMinThickness(self) -> float: if abs(self._min_thickness - sys.float_info.max) < 10: # Some lenience due to floating point rounding. return 0.0 # If it's still max-float, there are no measurements. Use 0 then. @@ -535,10 +597,8 @@ class SimulationView(CuraView): visible_indicies_with_extrusion = numpy.where(numpy.isin(polyline.types, visible_line_types_with_extrusion))[0] if visible_indices.size == 0: # No items to take maximum or minimum of. continue - self._lengths_of_polyline[layer_index] = polyline.lineLengths visible_feedrates = numpy.take(polyline.lineFeedrates, visible_indices) visible_feedrates_with_extrusion = numpy.take(polyline.lineFeedrates, visible_indicies_with_extrusion) - self._current_feedrates[layer_index] = polyline.lineFeedrates visible_linewidths = numpy.take(polyline.lineWidths, visible_indices) visible_linewidths_with_extrusion = numpy.take(polyline.lineWidths, visible_indicies_with_extrusion) visible_thicknesses = numpy.take(polyline.lineThicknesses, visible_indices) diff --git a/plugins/SimulationView/SimulationViewMainComponent.qml b/plugins/SimulationView/SimulationViewMainComponent.qml index 216095c15c..23cbf8c611 100644 --- a/plugins/SimulationView/SimulationViewMainComponent.qml +++ b/plugins/SimulationView/SimulationViewMainComponent.qml @@ -136,54 +136,19 @@ Item Timer { id: simulationTimer - interval: UM.SimulationView.simulationTime + interval: 1000 / 60 running: false repeat: true onTriggered: { - var currentPath = UM.SimulationView.currentPath - var numPaths = UM.SimulationView.numPaths - var currentLayer = UM.SimulationView.currentLayer - var numLayers = UM.SimulationView.numLayers - - // When the user plays the simulation, if the path slider is at the end of this layer, we start - // the simulation at the beginning of the current layer. - if (!isSimulationPlaying) - { - if (currentPath >= numPaths) - { - UM.SimulationView.setCurrentPath(0) - } - else - { - UM.SimulationView.setCurrentPath(currentPath + 1) - } - } - // If the simulation is already playing and we reach the end of a layer, then it automatically - // starts at the beginning of the next layer. - else - { - if (currentPath >= numPaths) - { - // At the end of the model, the simulation stops - if (currentLayer >= numLayers) - { - playButton.pauseSimulation() - } - else - { - UM.SimulationView.setCurrentLayer(currentLayer + 1) - UM.SimulationView.setCurrentPath(0) - } - } - else - { - UM.SimulationView.setCurrentPath(currentPath + 1) - } + // divide by 1000 to accont for ms to s conversion + const advance_time = simulationTimer.interval / 1000.0; + if (!UM.SimulationView.advanceTime(advance_time)) { + playButton.pauseSimulation(); } // The status must be set here instead of in the resumeSimulation function otherwise it won't work // correctly, because part of the logic is in this trigger function. - isSimulationPlaying = true + isSimulationPlaying = true; } } diff --git a/plugins/SimulationView/SimulationViewProxy.py b/plugins/SimulationView/SimulationViewProxy.py index 576281874c..bf449a99d1 100644 --- a/plugins/SimulationView/SimulationViewProxy.py +++ b/plugins/SimulationView/SimulationViewProxy.py @@ -2,7 +2,6 @@ # Cura is released under the terms of the LGPLv3 or higher. from typing import TYPE_CHECKING -import numpy from PyQt6.QtCore import QObject, pyqtSignal, pyqtProperty from UM.FlameProfiler import pyqtSlot from UM.Application import Application @@ -12,11 +11,6 @@ if TYPE_CHECKING: class SimulationViewProxy(QObject): - - S_TO_MS = 1000 - SPEED_OF_SIMULATION = 10 - FACTOR = S_TO_MS/SPEED_OF_SIMULATION - def __init__(self, simulation_view: "SimulationView", parent=None) -> None: super().__init__(parent) self._simulation_view = simulation_view @@ -56,17 +50,13 @@ class SimulationViewProxy(QObject): def numPaths(self): return self._simulation_view.getMaxPaths() - @pyqtProperty(int, notify=currentPathChanged) + @pyqtProperty(float, notify=currentPathChanged) def currentPath(self): return self._simulation_view.getCurrentPath() - @pyqtProperty(int, notify=currentPathChanged) - def simulationTime(self): - # Extracts the currents paths simulation time (in seconds) for the current path from the dict of simulation time of the current layer. - # We multiply the time with 100 to make it to ms from s.(Should be 1000 in real time). This scaling makes the simulation time 10x faster than the real time. - simulationTimeOfpath = self._simulation_view.getSimulationTime(self._simulation_view.getCurrentPath()) * SimulationViewProxy.FACTOR - # Since the timer cannot process time less than 1 ms, we put a lower limit here - return int(max(1, simulationTimeOfpath)) + @pyqtSlot(float, result=bool) + def advanceTime(self, duration: float) -> bool: + return self._simulation_view.advanceTime(duration) @pyqtProperty(int, notify=currentPathChanged) def minimumPath(self): @@ -92,8 +82,8 @@ class SimulationViewProxy(QObject): def setMinimumLayer(self, layer_num): self._simulation_view.setMinimumLayer(layer_num) - @pyqtSlot(int) - def setCurrentPath(self, path_num): + @pyqtSlot(float) + def setCurrentPath(self, path_num: float): self._simulation_view.setPath(path_num) @pyqtSlot(int) @@ -229,4 +219,3 @@ class SimulationViewProxy(QObject): self._simulation_view.activityChanged.disconnect(self._onActivityChanged) self._simulation_view.globalStackChanged.disconnect(self._onGlobalStackChanged) self._simulation_view.preferencesChanged.disconnect(self._onPreferencesChanged) - From 41efdbabe0faf7bd70fe2d759db19be3e6033414 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Wed, 20 Dec 2023 18:16:04 +0100 Subject: [PATCH 2/7] Path change possible by user - simulation speed increased - no buffering in between layers - fps made 30 CURA-7647 --- plugins/SimulationView/SimulationView.py | 43 +++++++++++-------- .../SimulationViewMainComponent.qml | 3 +- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 64ec0dfc1b..2663993f55 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -1,6 +1,6 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. - +import math import sys from PyQt6.QtCore import Qt @@ -58,6 +58,7 @@ class SimulationView(CuraView): LAYER_VIEW_TYPE_LINE_TYPE = 1 LAYER_VIEW_TYPE_FEEDRATE = 2 LAYER_VIEW_TYPE_THICKNESS = 3 + SIMULATION_FACTOR = 5 _no_layers_warning_preference = "view/no_layers_warning" @@ -97,6 +98,7 @@ class SimulationView(CuraView): self._min_line_width = sys.float_info.max self._min_flow_rate = sys.float_info.max self._max_flow_rate = sys.float_info.min + self._cumulative_line_duration ={} self._global_container_stack: Optional[ContainerStack] = None self._proxy = None @@ -196,21 +198,21 @@ class SimulationView(CuraView): left_i = 0 right_i = self._max_paths - 1 - total_duration, cumulative_line_duration = self.cumulativeLineDuration() + total_duration = self.cumulativeLineDuration() # make an educated guess about where to start i = int(right_i * max(0.0, min(1.0, self._current_time / total_duration))) # binary search for the correct path while left_i < right_i: - if cumulative_line_duration[i] <= self._current_time: + if self._cumulative_line_duration[self.getCurrentLayer()][i] <= self._current_time: left_i = i + 1 else: right_i = i i = int((left_i + right_i) / 2) - left_value = cumulative_line_duration[i - 1] if i > 0 else 0.0 - right_value = cumulative_line_duration[i] + left_value = self._cumulative_line_duration[self.getCurrentLayer()][i - 1] if i > 0 else 0.0 + right_value = self._cumulative_line_duration[self.getCurrentLayer()][i] assert (left_value <= self._current_time <= right_value) @@ -225,10 +227,7 @@ class SimulationView(CuraView): :param time_increase: The amount of time to advance (in seconds). :return: True if the time was advanced, False if the end of the simulation was reached. """ - total_duration, cumulative_line_duration = self.cumulativeLineDuration() - - # time ratio - time_increase = time_increase + total_duration = self.cumulativeLineDuration() if self._current_time + time_increase > total_duration: # If we have reached the end of the simulation, go to the next layer. @@ -244,15 +243,20 @@ class SimulationView(CuraView): self.setTime(self._current_time + time_increase) return True - def cumulativeLineDuration(self) -> Tuple[float, List[float]]: - # TODO: cache the total duration and cumulative line duration at each layer change event - cumulative_line_duration = [] - total_duration = 0.0 - for polyline in self.getLayerData().polygons: - for line_duration in list((polyline.lineLengths / polyline.lineFeedrates)[0]): - total_duration += line_duration - cumulative_line_duration.append(total_duration) - return total_duration, cumulative_line_duration + def cumulativeLineDuration(self) -> float: + # Make sure _cumulative_line_duration is initialized properly + if self.getCurrentLayer() not in self._cumulative_line_duration: + self._cumulative_line_duration[self.getCurrentLayer()] = [] + total_duration = 0.0 + for polyline in self.getLayerData().polygons: + for line_duration in list((polyline.lineLengths / polyline.lineFeedrates)[0]): + total_duration += line_duration / SimulationView.SIMULATION_FACTOR + self._cumulative_line_duration[self.getCurrentLayer()].append(total_duration) + + # Calculate the total duration using numpy.sum + total_duration = (self._cumulative_line_duration[self.getCurrentLayer()][-1]) + + return total_duration def getLayerData(self) -> Optional["LayerData"]: scene = self.getController().getScene() @@ -360,6 +364,9 @@ class SimulationView(CuraView): if self._current_path_num != value: self._current_path_num = min(max(value, 0), self._max_paths) self._minimum_path_num = min(self._minimum_path_num, self._current_path_num) + # update _current time when the path is changed by user + if self.getCurrentLayer() in self._cumulative_line_duration and self._current_path_num < self._max_paths and round(self._current_path_num)== self._current_path_num: + self._current_time = self._cumulative_line_duration[self.getCurrentLayer()][int(self._current_path_num)] self._startUpdateTopLayers() self.currentPathNumChanged.emit() diff --git a/plugins/SimulationView/SimulationViewMainComponent.qml b/plugins/SimulationView/SimulationViewMainComponent.qml index 23cbf8c611..0432cfc08d 100644 --- a/plugins/SimulationView/SimulationViewMainComponent.qml +++ b/plugins/SimulationView/SimulationViewMainComponent.qml @@ -127,6 +127,7 @@ Item function resumeSimulation() { UM.SimulationView.setSimulationRunning(true) + UM.SimulationView.setCurrentPath(UM.SimulationView.currentPath) simulationTimer.start() layerSlider.manuallyChanged = false pathSlider.manuallyChanged = false @@ -136,7 +137,7 @@ Item Timer { id: simulationTimer - interval: 1000 / 60 + interval: 1000 / 30 running: false repeat: true onTriggered: From e2e94b7f6b9252ad64641793ef9a4d283cff5bcb Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Thu, 21 Dec 2023 10:32:49 +0100 Subject: [PATCH 3/7] fps made 15 for lower GPU load - reinitialisation of cummulative_time for new model CURA-7647 --- plugins/SimulationView/SimulationView.py | 5 +++-- plugins/SimulationView/SimulationViewMainComponent.qml | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 2663993f55..bce7062a36 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -98,7 +98,7 @@ class SimulationView(CuraView): self._min_line_width = sys.float_info.max self._min_flow_rate = sys.float_info.max self._max_flow_rate = sys.float_info.min - self._cumulative_line_duration ={} + self._cumulative_line_duration = {} self._global_container_stack: Optional[ContainerStack] = None self._proxy = None @@ -253,7 +253,7 @@ class SimulationView(CuraView): total_duration += line_duration / SimulationView.SIMULATION_FACTOR self._cumulative_line_duration[self.getCurrentLayer()].append(total_duration) - # Calculate the total duration using numpy.sum + # total duration for a layer to simulate is the last element of the list total_duration = (self._cumulative_line_duration[self.getCurrentLayer()][-1]) return total_duration @@ -572,6 +572,7 @@ class SimulationView(CuraView): self._max_thickness = sys.float_info.min self._min_flow_rate = sys.float_info.max self._max_flow_rate = sys.float_info.min + self._cumulative_line_duration = {} # The colour scheme is only influenced by the visible lines, so filter the lines by if they should be visible. visible_line_types = [] diff --git a/plugins/SimulationView/SimulationViewMainComponent.qml b/plugins/SimulationView/SimulationViewMainComponent.qml index 0432cfc08d..d9e7a95bc1 100644 --- a/plugins/SimulationView/SimulationViewMainComponent.qml +++ b/plugins/SimulationView/SimulationViewMainComponent.qml @@ -137,12 +137,12 @@ Item Timer { id: simulationTimer - interval: 1000 / 30 + interval: 1000 / 15 running: false repeat: true onTriggered: { - // divide by 1000 to accont for ms to s conversion + // divide by 1000 to account for ms to s conversion const advance_time = simulationTimer.interval / 1000.0; if (!UM.SimulationView.advanceTime(advance_time)) { playButton.pauseSimulation(); From 3f8908f53a8207d2d75cab89618d22436df31ccd Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Thu, 21 Dec 2023 10:42:52 +0100 Subject: [PATCH 4/7] cleanup CURA-7647 --- plugins/SimulationView/SimulationView.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index bce7062a36..070a5c0345 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -1,6 +1,5 @@ # Copyright (c) 2021 Ultimaker B.V. # Cura is released under the terms of the LGPLv3 or higher. -import math import sys from PyQt6.QtCore import Qt @@ -40,7 +39,7 @@ from .SimulationViewProxy import SimulationViewProxy import numpy import os.path -from typing import Optional, TYPE_CHECKING, List, Tuple, cast +from typing import Optional, TYPE_CHECKING, List, cast if TYPE_CHECKING: from UM.Scene.SceneNode import SceneNode From 0de4f612b399cfe8b7a6c8059e9579489bcd739c Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Thu, 21 Dec 2023 14:13:16 +0100 Subject: [PATCH 5/7] fixing review comments. Cache only for the current layer CURA-7647 --- plugins/SimulationView/SimulationView.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 070a5c0345..1043c7659a 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -197,21 +197,22 @@ class SimulationView(CuraView): left_i = 0 right_i = self._max_paths - 1 - total_duration = self.cumulativeLineDuration() + cumulative_line_duration = self.cumulativeLineDuration() + total_duration = cumulative_line_duration[-1] # make an educated guess about where to start i = int(right_i * max(0.0, min(1.0, self._current_time / total_duration))) # binary search for the correct path while left_i < right_i: - if self._cumulative_line_duration[self.getCurrentLayer()][i] <= self._current_time: + if cumulative_line_duration[i] <= self._current_time: left_i = i + 1 else: right_i = i i = int((left_i + right_i) / 2) - left_value = self._cumulative_line_duration[self.getCurrentLayer()][i - 1] if i > 0 else 0.0 - right_value = self._cumulative_line_duration[self.getCurrentLayer()][i] + left_value = cumulative_line_duration[i - 1] if i > 0 else 0.0 + right_value = cumulative_line_duration[i] assert (left_value <= self._current_time <= right_value) @@ -226,7 +227,7 @@ class SimulationView(CuraView): :param time_increase: The amount of time to advance (in seconds). :return: True if the time was advanced, False if the end of the simulation was reached. """ - total_duration = self.cumulativeLineDuration() + total_duration = self.cumulativeLineDuration()[-1] if self._current_time + time_increase > total_duration: # If we have reached the end of the simulation, go to the next layer. @@ -242,9 +243,11 @@ class SimulationView(CuraView): self.setTime(self._current_time + time_increase) return True - def cumulativeLineDuration(self) -> float: + def cumulativeLineDuration(self) -> list: # Make sure _cumulative_line_duration is initialized properly if self.getCurrentLayer() not in self._cumulative_line_duration: + #clear cache + self._cumulative_line_duration = {} self._cumulative_line_duration[self.getCurrentLayer()] = [] total_duration = 0.0 for polyline in self.getLayerData().polygons: @@ -252,10 +255,7 @@ class SimulationView(CuraView): total_duration += line_duration / SimulationView.SIMULATION_FACTOR self._cumulative_line_duration[self.getCurrentLayer()].append(total_duration) - # total duration for a layer to simulate is the last element of the list - total_duration = (self._cumulative_line_duration[self.getCurrentLayer()][-1]) - - return total_duration + return self._cumulative_line_duration[self.getCurrentLayer()] def getLayerData(self) -> Optional["LayerData"]: scene = self.getController().getScene() @@ -364,8 +364,8 @@ class SimulationView(CuraView): self._current_path_num = min(max(value, 0), self._max_paths) self._minimum_path_num = min(self._minimum_path_num, self._current_path_num) # update _current time when the path is changed by user - if self.getCurrentLayer() in self._cumulative_line_duration and self._current_path_num < self._max_paths and round(self._current_path_num)== self._current_path_num: - self._current_time = self._cumulative_line_duration[self.getCurrentLayer()][int(self._current_path_num)] + if self._current_path_num < self._max_paths and round(self._current_path_num)== self._current_path_num: + self._current_time = self.cumulativeLineDuration()[int(self._current_path_num)] self._startUpdateTopLayers() self.currentPathNumChanged.emit() From 152cb27232310a35b3e71a70b438253a95c760f5 Mon Sep 17 00:00:00 2001 From: Saumya Jain <70144862+saumyaj3@users.noreply.github.com> Date: Thu, 21 Dec 2023 14:30:43 +0100 Subject: [PATCH 6/7] Update plugins/SimulationView/SimulationView.py review fix Co-authored-by: Casper Lamboo --- plugins/SimulationView/SimulationView.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 1043c7659a..04fc383fbb 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -243,7 +243,7 @@ class SimulationView(CuraView): self.setTime(self._current_time + time_increase) return True - def cumulativeLineDuration(self) -> list: + def cumulativeLineDuration(self) -> List[float]: # Make sure _cumulative_line_duration is initialized properly if self.getCurrentLayer() not in self._cumulative_line_duration: #clear cache From 411b40d78c39923723ad31709d04f5558c7d1728 Mon Sep 17 00:00:00 2001 From: "saumya.jain" Date: Fri, 22 Dec 2023 10:37:51 +0100 Subject: [PATCH 7/7] Simulation speed made 3X case where no polygon in layer is also addressed CURA-11289 --- plugins/SimulationView/SimulationView.py | 57 ++++++++++++------------ 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py index 04fc383fbb..337879475b 100644 --- a/plugins/SimulationView/SimulationView.py +++ b/plugins/SimulationView/SimulationView.py @@ -57,7 +57,7 @@ class SimulationView(CuraView): LAYER_VIEW_TYPE_LINE_TYPE = 1 LAYER_VIEW_TYPE_FEEDRATE = 2 LAYER_VIEW_TYPE_THICKNESS = 3 - SIMULATION_FACTOR = 5 + SIMULATION_FACTOR = 3 _no_layers_warning_preference = "view/no_layers_warning" @@ -192,33 +192,30 @@ class SimulationView(CuraView): return self._current_path_num def setTime(self, time: float) -> None: - self._current_time = time - - left_i = 0 - right_i = self._max_paths - 1 - cumulative_line_duration = self.cumulativeLineDuration() - total_duration = cumulative_line_duration[-1] + if len(cumulative_line_duration) > 0: + self._current_time = time + left_i = 0 + right_i = self._max_paths - 1 + total_duration = cumulative_line_duration[-1] + # make an educated guess about where to start + i = int(right_i * max(0.0, min(1.0, self._current_time / total_duration))) + # binary search for the correct path + while left_i < right_i: + if cumulative_line_duration[i] <= self._current_time: + left_i = i + 1 + else: + right_i = i + i = int((left_i + right_i) / 2) - # make an educated guess about where to start - i = int(right_i * max(0.0, min(1.0, self._current_time / total_duration))) + left_value = cumulative_line_duration[i - 1] if i > 0 else 0.0 + right_value = cumulative_line_duration[i] - # binary search for the correct path - while left_i < right_i: - if cumulative_line_duration[i] <= self._current_time: - left_i = i + 1 - else: - right_i = i - i = int((left_i + right_i) / 2) + assert (left_value <= self._current_time <= right_value) - left_value = cumulative_line_duration[i - 1] if i > 0 else 0.0 - right_value = cumulative_line_duration[i] + fractional_value = (self._current_time - left_value) / (right_value - left_value) - assert (left_value <= self._current_time <= right_value) - - fractional_value = (self._current_time - left_value) / (right_value - left_value) - - self.setPath(i + fractional_value) + self.setPath(i + fractional_value) def advanceTime(self, time_increase: float) -> bool: """ @@ -227,7 +224,9 @@ class SimulationView(CuraView): :param time_increase: The amount of time to advance (in seconds). :return: True if the time was advanced, False if the end of the simulation was reached. """ - total_duration = self.cumulativeLineDuration()[-1] + total_duration = 0.0 + if len(self.cumulativeLineDuration()) > 0: + total_duration = self.cumulativeLineDuration()[-1] if self._current_time + time_increase > total_duration: # If we have reached the end of the simulation, go to the next layer. @@ -250,10 +249,12 @@ class SimulationView(CuraView): self._cumulative_line_duration = {} self._cumulative_line_duration[self.getCurrentLayer()] = [] total_duration = 0.0 - for polyline in self.getLayerData().polygons: - for line_duration in list((polyline.lineLengths / polyline.lineFeedrates)[0]): - total_duration += line_duration / SimulationView.SIMULATION_FACTOR - self._cumulative_line_duration[self.getCurrentLayer()].append(total_duration) + polylines = self.getLayerData() + if polylines is not None: + for polyline in polylines.polygons: + for line_duration in list((polyline.lineLengths / polyline.lineFeedrates)[0]): + total_duration += line_duration / SimulationView.SIMULATION_FACTOR + self._cumulative_line_duration[self.getCurrentLayer()].append(total_duration) return self._cumulative_line_duration[self.getCurrentLayer()]