Split error checking into smaller sub-tasks

CURA-5059

Split stack error checking into smaller sub-tasks so running them on the Qt
thread will not block GUI updates from happening for too long.
This commit is contained in:
Lipu Fei 2018-03-13 13:21:41 +01:00
parent 40d3e09d90
commit 934d297e6c
4 changed files with 253 additions and 60 deletions

View file

@ -67,6 +67,8 @@ from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
from cura.Machines.MachineErrorChecker import MachineErrorChecker
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
@ -142,12 +144,6 @@ class CuraApplication(QtApplication):
Q_ENUMS(ResourceTypes) Q_ENUMS(ResourceTypes)
# FIXME: This signal belongs to the MachineManager, but the CuraEngineBackend plugin requires on it.
# Because plugins are initialized before the ContainerRegistry, putting this signal in MachineManager
# will make it initialized before ContainerRegistry does, and it won't find the active machine, thus
# Cura will always show the Add Machine Dialog upon start.
stacksValidationFinished = pyqtSignal() # Emitted whenever a validation is finished
def __init__(self, **kwargs): def __init__(self, **kwargs):
# this list of dir names will be used by UM to detect an old cura directory # this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]: for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "user", "variants"]:
@ -224,12 +220,14 @@ class CuraApplication(QtApplication):
self._machine_manager = None # This is initialized on demand. self._machine_manager = None # This is initialized on demand.
self._extruder_manager = None self._extruder_manager = None
self._material_manager = None self._material_manager = None
self._quality_manager = None
self._object_manager = None self._object_manager = None
self._build_plate_model = None self._build_plate_model = None
self._multi_build_plate_model = None self._multi_build_plate_model = None
self._setting_inheritance_manager = None self._setting_inheritance_manager = None
self._simple_mode_settings_manager = None self._simple_mode_settings_manager = None
self._cura_scene_controller = None self._cura_scene_controller = None
self._machine_error_checker = None
self._additional_components = {} # Components to add to certain areas in the interface self._additional_components = {} # Components to add to certain areas in the interface
@ -743,19 +741,28 @@ class CuraApplication(QtApplication):
self.preRun() self.preRun()
container_registry = ContainerRegistry.getInstance() container_registry = ContainerRegistry.getInstance()
Logger.log("i", "Initializing variant manager")
self._variant_manager = VariantManager(container_registry) self._variant_manager = VariantManager(container_registry)
self._variant_manager.initialize() self._variant_manager.initialize()
Logger.log("i", "Initializing material manager")
from cura.Machines.MaterialManager import MaterialManager from cura.Machines.MaterialManager import MaterialManager
self._material_manager = MaterialManager(container_registry, parent = self) self._material_manager = MaterialManager(container_registry, parent = self)
self._material_manager.initialize() self._material_manager.initialize()
Logger.log("i", "Initializing quality manager")
from cura.Machines.QualityManager import QualityManager from cura.Machines.QualityManager import QualityManager
self._quality_manager = QualityManager(container_registry, parent = self) self._quality_manager = QualityManager(container_registry, parent = self)
self._quality_manager.initialize() self._quality_manager.initialize()
Logger.log("i", "Initializing machine manager")
self._machine_manager = MachineManager(self) self._machine_manager = MachineManager(self)
Logger.log("i", "Initializing machine error checker")
self._machine_error_checker = MachineErrorChecker(self)
self._machine_error_checker.initialize()
# Check if we should run as single instance or not # Check if we should run as single instance or not
self._setUpSingleInstanceServer() self._setUpSingleInstanceServer()
@ -781,8 +788,11 @@ class CuraApplication(QtApplication):
self._openFile(file_name) self._openFile(file_name)
self.started = True self.started = True
self.initializationFinished.emit()
self.exec_() self.exec_()
initializationFinished = pyqtSignal()
## Run Cura without GUI elements and interaction (server mode). ## Run Cura without GUI elements and interaction (server mode).
def runWithoutGUI(self): def runWithoutGUI(self):
self._use_gui = False self._use_gui = False
@ -847,6 +857,9 @@ class CuraApplication(QtApplication):
def hasGui(self): def hasGui(self):
return self._use_gui return self._use_gui
def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
return self._machine_error_checker
def getMachineManager(self, *args) -> MachineManager: def getMachineManager(self, *args) -> MachineManager:
if self._machine_manager is None: if self._machine_manager is None:
self._machine_manager = MachineManager(self) self._machine_manager = MachineManager(self)

View file

@ -0,0 +1,184 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from collections import deque
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtProperty
from UM.Application import Application
from UM.Logger import Logger
#
# This class performs setting error checks for the currently active machine.
#
# The whole error checking process is pretty heavy which can take ~0.5 secs, so it can cause GUI to lag.
# The idea here is to split the whole error check into small tasks, each of which only checks a single setting key
# in a stack. According to my profiling results, the maximal runtime for such a sub-task is <0.03 secs, which should
# be good enough. Moreover, if any changes happened to the machine, we can cancel the check in progress without wait
# for it to finish the complete work.
#
class MachineErrorChecker(QObject):
def __init__(self, parent = None):
super().__init__(parent)
self._global_stack = None
self._has_errors = True # Result of the error check, indicating whether there are errors in the stack
self._error_keys = set() # A set of settings keys that have errors
self._error_keys_in_progress = set() # The variable that stores the results of the currently in progress check
self._stacks_to_check = None # a FIFO queue of stacks to check for errors
self._keys_to_check = None # a FIFO queue of setting keys to check for errors
self._need_to_check = False # Whether we need to schedule a new check or not. This flag is set when a new
# error check needs to take place while there is already one running at the moment.
self._check_in_progress = False # Whether there is an error check running in progress at the moment.
self._application = Application.getInstance()
self._machine_manager = self._application.getMachineManager()
# This timer delays the starting of error check so we can react less frequently if the user is frequently
# changing settings.
self._error_check_timer = QTimer(self)
self._error_check_timer.setInterval(300)
self._error_check_timer.setSingleShot(True)
def initialize(self):
self._error_check_timer.timeout.connect(self._rescheduleCheck)
# Reconnect all signals when the active machine gets changed.
self._machine_manager.globalContainerChanged.connect(self._onMachineChanged)
# Whenever the machine settings get changed, we schedule an error check.
self._machine_manager.globalContainerChanged.connect(self.startErrorCheck)
self._machine_manager.globalValueChanged.connect(self.startErrorCheck)
self._onMachineChanged()
def _onMachineChanged(self):
if self._global_stack:
self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
for extruder in self._global_stack.extruders.values():
extruder.propertyChanged.disconnect(self.startErrorCheck)
extruder.containersChanged.disconnect(self.startErrorCheck)
self._global_stack = self._machine_manager.activeMachine
if self._global_stack:
self._global_stack.propertyChanged.connect(self.startErrorCheck)
self._global_stack.containersChanged.connect(self.startErrorCheck)
for extruder in self._global_stack.extruders.values():
extruder.propertyChanged.connect(self.startErrorCheck)
extruder.containersChanged.connect(self.startErrorCheck)
hasErrorUpdated = pyqtSignal()
needToWaitForResultChanged = pyqtSignal()
errorCheckFinished = pyqtSignal()
@pyqtProperty(bool, notify = hasErrorUpdated)
def hasError(self) -> bool:
return self._has_errors
@pyqtProperty(bool, notify = needToWaitForResultChanged)
def needToWaitForResult(self) -> bool:
return self._need_to_check or self._check_in_progress
# Starts the error check timer to schedule a new error check.
def startErrorCheck(self, *args):
if not self._check_in_progress:
self._need_to_check = True
self.needToWaitForResultChanged.emit()
self._error_check_timer.start()
# This function is called by the timer to reschedule a new error check.
# If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
# to notify the current check to stop and start a new one.
def _rescheduleCheck(self):
if self._check_in_progress and not self._need_to_check:
self._need_to_check = True
self.needToWaitForResultChanged.emit()
return
self._error_keys_in_progress = set()
self._need_to_check = False
self.needToWaitForResultChanged.emit()
global_stack = self._machine_manager.activeMachine
if global_stack is None:
Logger.log("i", "No active machine, nothing to check.")
return
self._stacks_to_check = deque([global_stack] + list(global_stack.extruders.values()))
self._keys_to_check = deque(global_stack.getAllKeys())
self._application.callLater(self._checkStack)
Logger.log("d", "New error check scheduled.")
def _checkStack(self):
from UM.Settings.SettingDefinition import SettingDefinition
from UM.Settings.Validator import ValidatorState
if self._need_to_check:
Logger.log("d", "Need to check for errors again. Discard the current progress and reschedule a check.")
self._check_in_progress = False
self._application.callLater(self.startErrorCheck)
return
self._check_in_progress = True
# If there is nothing to check any more, it means there is no error.
if not self._stacks_to_check or not self._keys_to_check:
# Finish
self._setResult(False)
return
stack = self._stacks_to_check[0]
key = self._keys_to_check.popleft()
# If there is no key left in this stack, check the next stack later.
if not self._keys_to_check:
if len(self._stacks_to_check) == 1:
stacks = None
keys = None
else:
stack = self._stacks_to_check.popleft()
self._keys_to_check = deque(stack.getAllKeys())
enabled = stack.getProperty(key, "enabled")
if not enabled:
self._application.callLater(self._checkStack)
return
validation_state = stack.getProperty(key, "validationState")
if validation_state is None:
# Setting is not validated. This can happen if there is only a setting definition.
# We do need to validate it, because a setting definitions value can be set by a function, which could
# be an invalid setting.
definition = stack.getSettingDefinition(key)
validator_type = SettingDefinition.getValidatorForType(definition.type)
if validator_type:
validator = validator_type(key)
validation_state = validator(stack)
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
# Finish
self._setResult(True)
return
# Schedule the check for the next key
self._application.callLater(self._checkStack)
def _setResult(self, result: bool):
if result != self._has_errors:
self._has_errors = result
self.hasErrorUpdated.emit()
self._machine_manager.stacksValidationChanged.emit()
self._need_to_check = False
self._check_in_progress = False
self.needToWaitForResultChanged.emit()
self.errorCheckFinished.emit()
Logger.log("i", "Error check finished, result = %s", result)

View file

@ -4,7 +4,7 @@
import collections import collections
import time import time
#Type hinting. #Type hinting.
from typing import Union, List, Dict, TYPE_CHECKING, Optional from typing import List, Dict, TYPE_CHECKING, Optional
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Signal import Signal from UM.Signal import Signal
@ -20,7 +20,6 @@ from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.Settings.ContainerRegistry import ContainerRegistry from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.SettingFunction import SettingFunction from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique from UM.Signal import postponeSignals, CompressTechnique
@ -56,11 +55,6 @@ class MachineManager(QObject):
self.machine_extruder_material_update_dict = collections.defaultdict(list) self.machine_extruder_material_update_dict = collections.defaultdict(list)
self._error_check_timer = QTimer()
self._error_check_timer.setInterval(250)
self._error_check_timer.setSingleShot(True)
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
self._instance_container_timer = QTimer() self._instance_container_timer = QTimer()
self._instance_container_timer.setInterval(250) self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True) self._instance_container_timer.setSingleShot(True)
@ -228,15 +222,6 @@ class MachineManager(QObject):
del self.machine_extruder_material_update_dict[self._global_container_stack.getId()] del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
self.activeQualityGroupChanged.emit() self.activeQualityGroupChanged.emit()
self._error_check_timer.start()
## Update self._stacks_valid according to _checkStacksForErrors and emit if change.
def _updateStacksHaveErrors(self) -> None:
old_stacks_have_errors = self._stacks_have_errors
self._stacks_have_errors = self._checkStacksHaveErrors()
if old_stacks_have_errors != self._stacks_have_errors:
self.stacksValidationChanged.emit()
Application.getInstance().stacksValidationFinished.emit()
def _onActiveExtruderStackChanged(self) -> None: def _onActiveExtruderStackChanged(self) -> None:
self.blurSettings.emit() # Ensure no-one has focus. self.blurSettings.emit() # Ensure no-one has focus.
@ -256,8 +241,6 @@ class MachineManager(QObject):
self.rootMaterialChanged.emit() self.rootMaterialChanged.emit()
self._error_check_timer.start()
def _onInstanceContainersChanged(self, container) -> None: def _onInstanceContainersChanged(self, container) -> None:
self._instance_container_timer.start() self._instance_container_timer.start()
@ -266,9 +249,6 @@ class MachineManager(QObject):
# Notify UI items, such as the "changed" star in profile pull down menu. # Notify UI items, such as the "changed" star in profile pull down menu.
self.activeStackValueChanged.emit() self.activeStackValueChanged.emit()
elif property_name == "validationState":
self._error_check_timer.start()
## Given a global_stack, make sure that it's all valid by searching for this quality group and applying it again ## Given a global_stack, make sure that it's all valid by searching for this quality group and applying it again
def _initMachineState(self, global_stack): def _initMachineState(self, global_stack):
material_dict = {} material_dict = {}
@ -832,9 +812,10 @@ class MachineManager(QObject):
## This will fire the propertiesChanged for all settings so they will be updated in the front-end ## This will fire the propertiesChanged for all settings so they will be updated in the front-end
def forceUpdateAllSettings(self): def forceUpdateAllSettings(self):
property_names = ["value", "resolve"] with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
for setting_key in self._global_container_stack.getAllKeys(): property_names = ["value", "resolve"]
self._global_container_stack.propertiesChanged.emit(setting_key, property_names) for setting_key in self._global_container_stack.getAllKeys():
self._global_container_stack.propertiesChanged.emit(setting_key, property_names)
@pyqtSlot(int, bool) @pyqtSlot(int, bool)
def setExtruderEnabled(self, position: int, enabled) -> None: def setExtruderEnabled(self, position: int, enabled) -> None:

View file

@ -10,7 +10,6 @@ from UM.Logger import Logger
from UM.Message import Message from UM.Message import Message
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
from UM.Resources import Resources from UM.Resources import Resources
from UM.Settings.Validator import ValidatorState #To find if a setting is in an error state. We can't slice then.
from UM.Platform import Platform from UM.Platform import Platform
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
from UM.Qt.Duration import DurationFormat from UM.Qt.Duration import DurationFormat
@ -32,6 +31,7 @@ import Arcus
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura") catalog = i18nCatalog("cura")
class CuraEngineBackend(QObject, Backend): class CuraEngineBackend(QObject, Backend):
backendError = Signal() backendError = Signal()
@ -62,23 +62,26 @@ class CuraEngineBackend(QObject, Backend):
default_engine_location = execpath default_engine_location = execpath
break break
self._application = Application.getInstance()
self._multi_build_plate_model = None
self._machine_error_checker = None
if not default_engine_location: if not default_engine_location:
raise EnvironmentError("Could not find CuraEngine") raise EnvironmentError("Could not find CuraEngine")
Logger.log("i", "Found CuraEngine at: %s" %(default_engine_location)) Logger.log("i", "Found CuraEngine at: %s", default_engine_location)
default_engine_location = os.path.abspath(default_engine_location) default_engine_location = os.path.abspath(default_engine_location)
Preferences.getInstance().addPreference("backend/location", default_engine_location) Preferences.getInstance().addPreference("backend/location", default_engine_location)
# Workaround to disable layer view processing if layer view is not active. # Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False self._layer_view_active = False
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
Application.getInstance().getMultiBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._onActiveViewChanged() self._onActiveViewChanged()
self._stored_layer_data = [] self._stored_layer_data = []
self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._scene = Application.getInstance().getController().getScene() self._scene = self._application.getController().getScene()
self._scene.sceneChanged.connect(self._onSceneChanged) self._scene.sceneChanged.connect(self._onSceneChanged)
# Triggers for auto-slicing. Auto-slicing is triggered as follows: # Triggers for auto-slicing. Auto-slicing is triggered as follows:
@ -86,20 +89,10 @@ class CuraEngineBackend(QObject, Backend):
# - whenever there is a value change, we start the timer # - whenever there is a value change, we start the timer
# - sometimes an error check can get scheduled for a value change, in that case, we ONLY want to start the # - sometimes an error check can get scheduled for a value change, in that case, we ONLY want to start the
# auto-slicing timer when that error check is finished # auto-slicing timer when that error check is finished
# If there is an error check, it will set the "_is_error_check_scheduled" flag, stop the auto-slicing timer, # If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
# and only wait for the error check to be finished to start the auto-slicing timer again. # to start the auto-slicing timer again.
# #
self._global_container_stack = None self._global_container_stack = None
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)
# extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
ExtruderManager.getInstance().extrudersChanged.connect(self._extruderChanged)
# A flag indicating if an error check was scheduled
# If so, we will stop the auto-slice timer and start upon the error check
self._is_error_check_scheduled = False
# Listeners for receiving messages from the back-end. # Listeners for receiving messages from the back-end.
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
@ -125,13 +118,6 @@ class CuraEngineBackend(QObject, Backend):
self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool) self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
self.backendQuit.connect(self._onBackendQuit)
self.backendConnected.connect(self._onBackendConnected)
# When a tool operation is in progress, don't slice. So we need to listen for tool operations.
Application.getInstance().getController().toolOperationStarted.connect(self._onToolOperationStarted)
Application.getInstance().getController().toolOperationStopped.connect(self._onToolOperationStopped)
self._slice_start_time = None self._slice_start_time = None
Preferences.getInstance().addPreference("general/auto_slice", True) Preferences.getInstance().addPreference("general/auto_slice", True)
@ -146,6 +132,30 @@ class CuraEngineBackend(QObject, Backend):
self.determineAutoSlicing() self.determineAutoSlicing()
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged) Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
self._application.initializationFinished.connect(self.initialize)
def initialize(self):
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
self._application.getController().activeViewChanged.connect(self._onActiveViewChanged)
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveViewChanged)
self._application.globalContainerStackChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
# extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
ExtruderManager.getInstance().extrudersChanged.connect(self._extruderChanged)
self.backendQuit.connect(self._onBackendQuit)
self.backendConnected.connect(self._onBackendConnected)
# When a tool operation is in progress, don't slice. So we need to listen for tool operations.
self._application.getController().toolOperationStarted.connect(self._onToolOperationStarted)
self._application.getController().toolOperationStopped.connect(self._onToolOperationStopped)
self._machine_error_checker = self._application.getMachineErrorChecker()
self._machine_error_checker.errorCheckFinished.connect(self._onStackErrorCheckFinished)
## Terminate the engine process. ## Terminate the engine process.
# #
# This function should terminate the engine process. # This function should terminate the engine process.
@ -531,11 +541,9 @@ class CuraEngineBackend(QObject, Backend):
elif property == "validationState": elif property == "validationState":
if self._use_timer: if self._use_timer:
self._is_error_check_scheduled = True
self._change_timer.stop() self._change_timer.stop()
def _onStackErrorCheckFinished(self): def _onStackErrorCheckFinished(self):
self._is_error_check_scheduled = False
if not self._slicing and self._build_plates_to_be_sliced: if not self._slicing and self._build_plates_to_be_sliced:
self.needsSlicing() self.needsSlicing()
self._onChanged() self._onChanged()
@ -561,12 +569,15 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(message.amount) self.processingProgress.emit(message.amount)
self.backendStateChange.emit(BackendState.Processing) self.backendStateChange.emit(BackendState.Processing)
# testing
def _invokeSlice(self): def _invokeSlice(self):
if self._use_timer: if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# otherwise business as usual # otherwise business as usual
if self._is_error_check_scheduled: if self._machine_error_checker is None:
self._change_timer.stop()
return
if self._machine_error_checker.needToWaitForResult:
self._change_timer.stop() self._change_timer.stop()
else: else:
self._change_timer.start() self._change_timer.start()
@ -632,7 +643,11 @@ class CuraEngineBackend(QObject, Backend):
if self._use_timer: if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice, # if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
# otherwise business as usual # otherwise business as usual
if self._is_error_check_scheduled: if self._machine_error_checker is None:
self._change_timer.stop()
return
if self._machine_error_checker.needToWaitForResult:
self._change_timer.stop() self._change_timer.stop()
else: else:
self._change_timer.start() self._change_timer.start()
@ -786,7 +801,7 @@ class CuraEngineBackend(QObject, Backend):
self._change_timer.start() self._change_timer.start()
def _extruderChanged(self): def _extruderChanged(self):
for build_plate_number in range(Application.getInstance().getMultiBuildPlateModel().maxBuildPlate + 1): for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
if build_plate_number not in self._build_plates_to_be_sliced: if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number) self._build_plates_to_be_sliced.append(build_plate_number)
self._invokeSlice() self._invokeSlice()