Move the start of slicing to a proper job

This way it can be properly threaded (with a generous sprinkling of
"yieldThread") so we do not block the UI when slicing starts.

Contributes to CURA-358
This commit is contained in:
Arjen Hiemstra 2015-11-04 14:08:20 +01:00
parent 20b828ecee
commit cb05aee391
2 changed files with 54 additions and 152 deletions

View file

@ -17,6 +17,7 @@ from cura.OneAtATimeIterator import OneAtATimeIterator
from . import Cura_pb2 from . import Cura_pb2
from . import ProcessSlicedObjectListJob from . import ProcessSlicedObjectListJob
from . import ProcessGCodeJob from . import ProcessGCodeJob
from . import StartSliceJob
import os import os
import sys import sys
@ -67,12 +68,8 @@ class CuraEngineBackend(Backend):
self._slicing = False self._slicing = False
self._restart = False self._restart = False
self._save_gcode = True
self._save_polygons = True
self._report_progress = True
self._enabled = True self._enabled = True
self._always_restart = True
self._message = None self._message = None
@ -97,24 +94,12 @@ class CuraEngineBackend(Backend):
## Emitted whne the slicing process is aborted forcefully. ## Emitted whne the slicing process is aborted forcefully.
slicingCancelled = Signal() slicingCancelled = Signal()
## Perform a slice of the scene with the given set of settings. ## Perform a slice of the scene.
# def slice(self):
# \param kwargs Keyword arguments.
# Valid values are:
# - settings: The settings to use for the slice. The default is the active machine.
# - save_gcode: True if the generated gcode should be saved, False if not. True by default.
# - save_polygons: True if the generated polygon data should be saved, False if not. True by default.
# - force_restart: True if the slicing process should be forcefully restarted if it is already slicing.
# If False, this method will do nothing when already slicing. True by default.
# - report_progress: True if the slicing progress should be reported, False if not. Default is True.
def slice(self, **kwargs):
if not self._enabled: if not self._enabled:
return return
if self._slicing: if self._slicing:
if not kwargs.get("force_restart", True):
return
self._slicing = False self._slicing = False
self._restart = True self._restart = True
if self._process is not None: if self._process is not None:
@ -123,41 +108,15 @@ class CuraEngineBackend(Backend):
self._process.terminate() self._process.terminate()
except: # terminating a process that is already terminating causes an exception, silently ignore this. except: # terminating a process that is already terminating causes an exception, silently ignore this.
pass pass
self.slicingCancelled.emit()
return
Logger.log("d", "Preparing to send slice data to engine.")
object_groups = []
if self._profile.getSettingValue("print_sequence") == "one_at_a_time":
for node in OneAtATimeIterator(self._scene.getRoot()):
temp_list = []
children = node.getAllChildren()
children.append(node)
for child_node in children:
if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
temp_list.append(child_node)
object_groups.append(temp_list)
else:
temp_list = []
for node in DepthFirstIterator(self._scene.getRoot()):
if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
if not getattr(node, "_outside_buildarea", False):
temp_list.append(node)
if len(temp_list) == 0:
self.processingProgress.emit(0.0)
return
object_groups.append(temp_list)
#for node in DepthFirstIterator(self._scene.getRoot()):
# if type(node) is SceneNode and node.getMeshData() and node.getMeshData().getVertices() is not None:
# if not getattr(node, "_outside_buildarea", False):
# objects.append(node)
if len(object_groups) == 0:
if self._message: if self._message:
self._message.hide() self._message.hide()
self._message = None self._message = None
return #No point in slicing an empty build plate
if kwargs.get("profile", self._profile).hasErrorValue(): self.slicingCancelled.emit()
return
if self._profile.hasErrorValue():
Logger.log('w', "Profile has error values. Aborting slicing") Logger.log('w', "Profile has error values. Aborting slicing")
if self._message: if self._message:
self._message.hide() self._message.hide()
@ -165,18 +124,7 @@ class CuraEngineBackend(Backend):
self._message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors.")) self._message = Message(catalog.i18nc("@info:status", "Unable to slice. Please check your setting values for errors."))
self._message.show() self._message.show()
return #No slicing if we have error values since those are by definition illegal values. return #No slicing if we have error values since those are by definition illegal values.
# Remove existing layer data (if any)
for node in DepthFirstIterator(self._scene.getRoot()):
if type(node) is SceneNode and node.getMeshData():
if node.callDecoration("getLayerData"):
Application.getInstance().getController().getScene().getRoot().removeChild(node)
break
Application.getInstance().getController().getScene().gcode_list = None
self._slicing = True
self.slicingStarted.emit()
self._report_progress = kwargs.get("report_progress", True)
if self._report_progress:
self.processingProgress.emit(0.0) self.processingProgress.emit(0.0)
if not self._message: if not self._message:
self._message = Message(catalog.i18nc("@info:status", "Slicing..."), 0, False, -1) self._message = Message(catalog.i18nc("@info:status", "Slicing..."), 0, False, -1)
@ -184,43 +132,19 @@ class CuraEngineBackend(Backend):
else: else:
self._message.setProgress(-1) self._message.setProgress(-1)
self._sendSettings(kwargs.get("profile", self._profile)) self._scene.gcode_list = []
self._slicing = True
self._scene.acquireLock() job = StartSliceJob.StartSliceJob(self._profile, self._socket)
job.start()
job.finished.connect(self._onStartSliceCompleted)
# Set the gcode as an empty list. This will be filled with strings by GCodeLayer messages. def _onStartSliceCompleted(self, job):
# This is done so the gcode can be fragmented in memory and does not need a continues memory space. if job.getError() or job.getResult() != True:
# (AKA. This prevents MemoryErrors) if self._message:
self._save_gcode = kwargs.get("save_gcode", True) self._message.hide()
if self._save_gcode: self._message = None
setattr(self._scene, "gcode_list", []) return
self._save_polygons = kwargs.get("save_polygons", True)
slice_message = Cura_pb2.Slice()
for group in object_groups:
group_message = slice_message.object_lists.add()
for object in group:
mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation())
obj = group_message.objects.add()
obj.id = id(object)
verts = numpy.array(mesh_data.getVertices())
verts[:,[1,2]] = verts[:,[2,1]]
verts[:,1] *= -1
obj.vertices = verts.tostring()
self._handlePerObjectSettings(object, obj)
# Hack to add per-object settings also to the "MeshGroup" in CuraEngine
# We really should come up with a better solution for this.
self._handlePerObjectSettings(group[0], group_message)
self._scene.releaseLock()
Logger.log("d", "Sending data to engine for slicing.")
self._socket.sendMessage(slice_message)
def _onSceneChanged(self, source): def _onSceneChanged(self, source):
if type(source) is not SceneNode: if type(source) is not SceneNode:
@ -250,7 +174,6 @@ class CuraEngineBackend(Backend):
self._onChanged() self._onChanged()
def _onSlicedObjectListMessage(self, message): def _onSlicedObjectListMessage(self, message):
if self._save_polygons:
if self._layer_view_active: if self._layer_view_active:
job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(message) job = ProcessSlicedObjectListJob.ProcessSlicedObjectListJob(message)
job.start() job.start()
@ -258,7 +181,21 @@ class CuraEngineBackend(Backend):
self._stored_layer_data = message self._stored_layer_data = message
def _onProgressMessage(self, message): def _onProgressMessage(self, message):
if message.amount >= 0.99: if self._message:
self._message.setProgress(round(message.amount * 100))
self.processingProgress.emit(message.amount)
def _onGCodeLayerMessage(self, message):
self._scene.gcode_list.append(message.data.decode("utf-8", "replace"))
def _onGCodePrefixMessage(self, message):
self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace"))
def _onObjectPrintTimeMessage(self, message):
self.printDurationMessage.emit(message.time, message.material_amount)
self.processingProgress.emit(1.0)
self._slicing = False self._slicing = False
if self._message: if self._message:
@ -266,24 +203,12 @@ class CuraEngineBackend(Backend):
self._message.hide() self._message.hide()
self._message = None self._message = None
if self._message: if self._always_restart:
self._message.setProgress(round(message.amount * 100)) try:
self._process.terminate()
if self._report_progress: self._createSocket()
self.processingProgress.emit(message.amount) except: # terminating a process that is already terminating causes an exception, silently ignore this.
pass
def _onGCodeLayerMessage(self, message):
if self._save_gcode:
job = ProcessGCodeJob.ProcessGCodeLayerJob(message)
job.start()
def _onGCodePrefixMessage(self, message):
if self._save_gcode:
self._scene.gcode_list.insert(0, message.data.decode("utf-8", "replace"))
def _onObjectPrintTimeMessage(self, message):
self.printDurationMessage.emit(message.time, message.material_amount)
self.processingProgress.emit(1.0)
def _createSocket(self): def _createSocket(self):
super()._createSocket() super()._createSocket()
@ -306,15 +231,6 @@ class CuraEngineBackend(Backend):
self._change_timer.start() self._change_timer.start()
def _sendSettings(self, profile):
msg = Cura_pb2.SettingList()
for key, value in profile.getAllSettingValues(include_machine = True).items():
s = msg.settings.add()
s.name = key
s.value = str(value).encode("utf-8")
self._socket.sendMessage(msg)
def _onBackendConnected(self): def _onBackendConnected(self):
if self._restart: if self._restart:
self._onChanged() self._onChanged()
@ -338,20 +254,3 @@ class CuraEngineBackend(Backend):
self._stored_layer_data = None self._stored_layer_data = None
else: else:
self._layer_view_active = False self._layer_view_active = False
def _handlePerObjectSettings(self, node, message):
profile = node.callDecoration("getProfile")
if profile:
for key, value in profile.getChangedSettingValues().items():
setting = message.settings.add()
setting.name = key
setting.value = str(value).encode()
object_settings = node.callDecoration("getAllSettingValues")
if not object_settings:
return
for key, value in object_settings.items():
setting = message.settings.add()
setting.name = key
setting.value = str(value).encode()

View file

@ -15,6 +15,7 @@ from cura.OneAtATimeIterator import OneAtATimeIterator
from . import Cura_pb2 from . import Cura_pb2
## Job class that handles sending the current scene data to CuraEngine
class StartSliceJob(Job): class StartSliceJob(Job):
def __init__(self, profile, socket): def __init__(self, profile, socket):
super().__init__() super().__init__()
@ -45,6 +46,7 @@ class StartSliceJob(Job):
if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None: if type(child_node) is SceneNode and child_node.getMeshData() and child_node.getMeshData().getVertices() is not None:
temp_list.append(child_node) temp_list.append(child_node)
if temp_list:
object_groups.append(temp_list) object_groups.append(temp_list)
Job.yieldThread() Job.yieldThread()
else: else:
@ -54,6 +56,8 @@ class StartSliceJob(Job):
if not getattr(node, "_outside_buildarea", False): if not getattr(node, "_outside_buildarea", False):
temp_list.append(node) temp_list.append(node)
Job.yieldThread() Job.yieldThread()
if temp_list:
object_groups.append(temp_list) object_groups.append(temp_list)
self._scene.releaseLock() self._scene.releaseLock()
@ -68,7 +72,6 @@ class StartSliceJob(Job):
for group in object_groups: for group in object_groups:
group_message = slice_message.object_lists.add() group_message = slice_message.object_lists.add()
for object in group: for object in group:
print(object)
mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation()) mesh_data = object.getMeshData().getTransformed(object.getWorldTransformation())
obj = group_message.objects.add() obj = group_message.objects.add()