Convert doxygen to rst for CuraEngineBackend

This commit is contained in:
Nino van Hooff 2020-05-08 15:23:51 +02:00
parent 8f3827d5ae
commit 797d6ed438
3 changed files with 220 additions and 143 deletions

View file

@ -42,12 +42,14 @@ catalog = i18nCatalog("cura")
class CuraEngineBackend(QObject, Backend):
backendError = Signal()
## Starts the back-end plug-in.
#
# This registers all the signal listeners and prepares for communication
# with the back-end in general.
# CuraEngineBackend is exposed to qml as well.
def __init__(self) -> None:
"""Starts the back-end plug-in.
This registers all the signal listeners and prepares for communication
with the back-end in general.
CuraEngineBackend is exposed to qml as well.
"""
super().__init__()
# Find out where the engine is located, and how it is called.
# This depends on how Cura is packaged and which OS we are running on.
@ -177,18 +179,22 @@ class CuraEngineBackend(QObject, Backend):
self._machine_error_checker = self._application.getMachineErrorChecker()
self._machine_error_checker.errorCheckFinished.connect(self._onStackErrorCheckFinished)
## Terminate the engine process.
#
# This function should terminate the engine process.
# Called when closing the application.
def close(self) -> None:
"""Terminate the engine process.
This function should terminate the engine process.
Called when closing the application.
"""
# Terminate CuraEngine if it is still running at this point
self._terminate()
## Get the command that is used to call the engine.
# This is useful for debugging and used to actually start the engine.
# \return list of commands and args / parameters.
def getEngineCommand(self) -> List[str]:
"""Get the command that is used to call the engine.
This is useful for debugging and used to actually start the engine.
:return: list of commands and args / parameters.
"""
command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), ""]
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
@ -199,17 +205,18 @@ class CuraEngineBackend(QObject, Backend):
return command
## Emitted when we get a message containing print duration and material amount.
# This also implies the slicing has finished.
# \param time The amount of time the print will take.
# \param material_amount The amount of material the print will use.
printDurationMessage = Signal()
## Emitted when the slicing process starts.
"""Emitted when we get a message containing print duration and material amount.
This also implies the slicing has finished.
:param time: The amount of time the print will take.
:param material_amount: The amount of material the print will use.
"""
slicingStarted = Signal()
"""Emitted when the slicing process starts."""
## Emitted when the slicing process is aborted forcefully.
slicingCancelled = Signal()
"""Emitted when the slicing process is aborted forcefully."""
@pyqtSlot()
def stopSlicing(self) -> None:
@ -226,14 +233,16 @@ class CuraEngineBackend(QObject, Backend):
if self._error_message:
self._error_message.hide()
## Manually triggers a reslice
@pyqtSlot()
def forceSlice(self) -> None:
"""Manually triggers a reslice"""
self.markSliceAll()
self.slice()
## Perform a slice of the scene.
def slice(self) -> None:
"""Perform a slice of the scene."""
Logger.log("i", "Starting to slice...")
self._slice_start_time = time()
if not self._build_plates_to_be_sliced:
@ -289,9 +298,11 @@ class CuraEngineBackend(QObject, Backend):
self._start_slice_job.start()
self._start_slice_job.finished.connect(self._onStartSliceCompleted)
## Terminate the engine process.
# Start the engine process by calling _createSocket()
def _terminate(self) -> None:
"""Terminate the engine process.
Start the engine process by calling _createSocket()
"""
self._slicing = False
self._stored_layer_data = []
if self._start_slice_job_build_plate in self._stored_optimized_layer_data:
@ -316,15 +327,17 @@ class CuraEngineBackend(QObject, Backend):
except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
Logger.log("d", "Exception occurred while trying to kill the engine %s", str(e))
## Event handler to call when the job to initiate the slicing process is
# completed.
#
# When the start slice job is successfully completed, it will be happily
# slicing. This function handles any errors that may occur during the
# bootstrapping of a slice job.
#
# \param job The start slice job that was just finished.
def _onStartSliceCompleted(self, job: StartSliceJob) -> None:
"""Event handler to call when the job to initiate the slicing process is
completed.
When the start slice job is successfully completed, it will be happily
slicing. This function handles any errors that may occur during the
bootstrapping of a slice job.
:param job: The start slice job that was just finished.
"""
if self._error_message:
self._error_message.hide()
@ -443,11 +456,13 @@ class CuraEngineBackend(QObject, Backend):
if self._slice_start_time:
Logger.log("d", "Sending slice message took %s seconds", time() - self._slice_start_time )
## Determine enable or disable auto slicing. Return True for enable timer and False otherwise.
# It disables when
# - preference auto slice is off
# - decorator isBlockSlicing is found (used in g-code reader)
def determineAutoSlicing(self) -> bool:
"""Determine enable or disable auto slicing. Return True for enable timer and False otherwise.
It disables when:
- preference auto slice is off
- decorator isBlockSlicing is found (used in g-code reader)
"""
enable_timer = True
self._is_disabled = False
@ -472,8 +487,9 @@ class CuraEngineBackend(QObject, Backend):
self.disableTimer()
return False
## Return a dict with number of objects per build plate
def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
"""Return a dict with number of objects per build plate"""
num_objects = defaultdict(int) #type: Dict[int, int]
for node in DepthFirstIterator(self._scene.getRoot()):
# Only count sliceable objects
@ -483,12 +499,14 @@ class CuraEngineBackend(QObject, Backend):
num_objects[build_plate_number] += 1
return num_objects
## Listener for when the scene has changed.
#
# This should start a slice if the scene is now ready to slice.
#
# \param source The scene node that was changed.
def _onSceneChanged(self, source: SceneNode) -> None:
"""Listener for when the scene has changed.
This should start a slice if the scene is now ready to slice.
:param source: The scene node that was changed.
"""
if not source.callDecoration("isSliceable"):
return
@ -536,10 +554,12 @@ class CuraEngineBackend(QObject, Backend):
self._invokeSlice()
## Called when an error occurs in the socket connection towards the engine.
#
# \param error The exception that occurred.
def _onSocketError(self, error: Arcus.Error) -> None:
"""Called when an error occurs in the socket connection towards the engine.
:param error: The exception that occurred.
"""
if self._application.isShuttingDown():
return
@ -567,8 +587,9 @@ class CuraEngineBackend(QObject, Backend):
break
return has_slicable
## Remove old layer data (if any)
def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
"""Remove old layer data (if any)"""
# Clear out any old gcode
self._scene.gcode_dict = {} # type: ignore
@ -583,8 +604,9 @@ class CuraEngineBackend(QObject, Backend):
if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number)
## Convenient function: mark everything to slice, emit state and clear layer data
def needsSlicing(self) -> None:
"""Convenient function: mark everything to slice, emit state and clear layer data"""
# CURA-6604: If there's no slicable object, do not (try to) trigger slice, which will clear all the current
# gcode. This can break Gcode file loading if it tries to remove it afterwards.
if not self.hasSlicableObject():
@ -597,10 +619,12 @@ class CuraEngineBackend(QObject, Backend):
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData()
## A setting has changed, so check if we must reslice.
# \param instance The setting instance that has changed.
# \param property The property of the setting instance that has changed.
def _onSettingChanged(self, instance: SettingInstance, property: str) -> None:
"""A setting has changed, so check if we must reslice.
:param instance: The setting instance that has changed.
:param property: The property of the setting instance that has changed.
"""
if property == "value": # Only reslice if the value has changed.
self.needsSlicing()
self._onChanged()
@ -618,25 +642,31 @@ class CuraEngineBackend(QObject, Backend):
self.needsSlicing()
self._onChanged()
## Called when a sliced layer data message is received from the engine.
#
# \param message The protobuf message containing sliced layer data.
def _onLayerMessage(self, message: Arcus.PythonMessage) -> None:
"""Called when a sliced layer data message is received from the engine.
:param message: The protobuf message containing sliced layer data.
"""
self._stored_layer_data.append(message)
## Called when an optimized sliced layer data message is received from the engine.
#
# \param message The protobuf message containing sliced layer data.
def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None:
"""Called when an optimized sliced layer data message is received from the engine.
:param message: The protobuf message containing sliced layer data.
"""
if self._start_slice_job_build_plate is not None:
if self._start_slice_job_build_plate not in self._stored_optimized_layer_data:
self._stored_optimized_layer_data[self._start_slice_job_build_plate] = []
self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
## Called when a progress message is received from the engine.
#
# \param message The protobuf message containing the slicing progress.
def _onProgressMessage(self, message: Arcus.PythonMessage) -> None:
"""Called when a progress message is received from the engine.
:param message: The protobuf message containing the slicing progress.
"""
self.processingProgress.emit(message.amount)
self.setState(BackendState.Processing)
@ -653,10 +683,12 @@ class CuraEngineBackend(QObject, Backend):
else:
self._change_timer.start()
## Called when the engine sends a message that slicing is finished.
#
# \param message The protobuf message signalling that slicing is finished.
def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None:
"""Called when the engine sends a message that slicing is finished.
:param message: The protobuf message signalling that slicing is finished.
"""
self.setState(BackendState.Done)
self.processingProgress.emit(1.0)
@ -698,27 +730,32 @@ class CuraEngineBackend(QObject, Backend):
self.enableTimer() # manually enable timer to be able to invoke slice, also when in manual slice mode
self._invokeSlice()
## Called when a g-code message is received from the engine.
#
# \param message The protobuf message containing g-code, encoded as UTF-8.
def _onGCodeLayerMessage(self, message: Arcus.PythonMessage) -> None:
"""Called when a g-code message is received from the engine.
:param message: The protobuf message containing g-code, encoded as UTF-8.
"""
try:
self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
pass # Throw the message away.
## Called when a g-code prefix message is received from the engine.
#
# \param message The protobuf message containing the g-code prefix,
# encoded as UTF-8.
def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None:
"""Called when a g-code prefix message is received from the engine.
:param message: The protobuf message containing the g-code prefix,
encoded as UTF-8.
"""
try:
self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
except KeyError: # Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
pass # Throw the message away.
## Creates a new socket connection.
def _createSocket(self, protocol_file: str = None) -> None:
"""Creates a new socket connection."""
if not protocol_file:
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if not plugin_path:
@ -728,10 +765,12 @@ class CuraEngineBackend(QObject, Backend):
super()._createSocket(protocol_file)
self._engine_is_fresh = True
## Called when anything has changed to the stuff that needs to be sliced.
#
# This indicates that we should probably re-slice soon.
def _onChanged(self, *args: Any, **kwargs: Any) -> None:
"""Called when anything has changed to the stuff that needs to be sliced.
This indicates that we should probably re-slice soon.
"""
self.needsSlicing()
if self._use_timer:
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
@ -745,11 +784,13 @@ class CuraEngineBackend(QObject, Backend):
else:
self._change_timer.start()
## Called when a print time message is received from the engine.
#
# \param message The protobuf message containing the print time per feature and
# material amount per extruder
def _onPrintTimeMaterialEstimates(self, message: Arcus.PythonMessage) -> None:
"""Called when a print time message is received from the engine.
:param message: The protobuf message containing the print time per feature and
material amount per extruder
"""
material_amounts = []
for index in range(message.repeatedMessageCount("materialEstimates")):
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
@ -757,10 +798,12 @@ class CuraEngineBackend(QObject, Backend):
times = self._parseMessagePrintTimes(message)
self.printDurationMessage.emit(self._start_slice_job_build_plate, times, material_amounts)
## Called for parsing message to retrieve estimated time per feature
#
# \param message The protobuf message containing the print time per feature
def _parseMessagePrintTimes(self, message: Arcus.PythonMessage) -> Dict[str, float]:
"""Called for parsing message to retrieve estimated time per feature
:param message: The protobuf message containing the print time per feature
"""
result = {
"inset_0": message.time_inset_0,
"inset_x": message.time_inset_x,
@ -777,19 +820,22 @@ class CuraEngineBackend(QObject, Backend):
}
return result
## Called when the back-end connects to the front-end.
def _onBackendConnected(self) -> None:
"""Called when the back-end connects to the front-end."""
if self._restart:
self._restart = False
self._onChanged()
## Called when the user starts using some tool.
#
# When the user starts using a tool, we should pause slicing to prevent
# continuously slicing while the user is dragging some tool handle.
#
# \param tool The tool that the user is using.
def _onToolOperationStarted(self, tool: Tool) -> None:
"""Called when the user starts using some tool.
When the user starts using a tool, we should pause slicing to prevent
continuously slicing while the user is dragging some tool handle.
:param tool: The tool that the user is using.
"""
self._tool_active = True # Do not react on scene change
self.disableTimer()
# Restart engine as soon as possible, we know we want to slice afterwards
@ -797,12 +843,14 @@ class CuraEngineBackend(QObject, Backend):
self._terminate()
self._createSocket()
## Called when the user stops using some tool.
#
# This indicates that we can safely start slicing again.
#
# \param tool The tool that the user was using.
def _onToolOperationStopped(self, tool: Tool) -> None:
"""Called when the user stops using some tool.
This indicates that we can safely start slicing again.
:param tool: The tool that the user was using.
"""
self._tool_active = False # React on scene change again
self.determineAutoSlicing() # Switch timer on if appropriate
# Process all the postponed scene changes
@ -816,8 +864,9 @@ class CuraEngineBackend(QObject, Backend):
self._process_layers_job.finished.connect(self._onProcessLayersFinished)
self._process_layers_job.start()
## Called when the user changes the active view mode.
def _onActiveViewChanged(self) -> None:
"""Called when the user changes the active view mode."""
view = self._application.getController().getActiveView()
if view:
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate
@ -835,17 +884,20 @@ class CuraEngineBackend(QObject, Backend):
else:
self._layer_view_active = False
## Called when the back-end self-terminates.
#
# We should reset our state and start listening for new connections.
def _onBackendQuit(self) -> None:
"""Called when the back-end self-terminates.
We should reset our state and start listening for new connections.
"""
if not self._restart:
if self._process: # type: ignore
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait()) # type: ignore
self._process = None # type: ignore
## Called when the global container stack changes
def _onGlobalStackChanged(self) -> None:
"""Called when the global container stack changes"""
if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged)
self._global_container_stack.containersChanged.disconnect(self._onChanged)
@ -874,15 +926,18 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("d", "See if there is more to slice(2)...")
self._invokeSlice()
## Connect slice function to timer.
def enableTimer(self) -> None:
"""Connect slice function to timer."""
if not self._use_timer:
self._change_timer.timeout.connect(self.slice)
self._use_timer = True
## Disconnect slice function from timer.
# This means that slicing will not be triggered automatically
def disableTimer(self) -> None:
"""Disconnect slice function from timer.
This means that slicing will not be triggered automatically
"""
if self._use_timer:
self._use_timer = False
self._change_timer.timeout.disconnect(self.slice)
@ -894,8 +949,9 @@ class CuraEngineBackend(QObject, Backend):
if auto_slice:
self._change_timer.start()
## Tickle the backend so in case of auto slicing, it starts the timer.
def tickle(self) -> None:
"""Tickle the backend so in case of auto slicing, it starts the timer."""
if self._use_timer:
self._change_timer.start()

View file

@ -28,10 +28,12 @@ from cura.Machines.Models.ExtrudersModel import ExtrudersModel
catalog = i18nCatalog("cura")
## Return a 4-tuple with floats 0-1 representing the html color code
#
# \param color_code html color code, i.e. "#FF0000" -> red
def colorCodeToRGBA(color_code):
"""Return a 4-tuple with floats 0-1 representing the html color code
:param color_code: html color code, i.e. "#FF0000" -> red
"""
if color_code is None:
Logger.log("w", "Unable to convert color code, returning default")
return [0, 0, 0, 1]
@ -51,13 +53,15 @@ class ProcessSlicedLayersJob(Job):
self._abort_requested = False
self._build_plate_number = None
## Aborts the processing of layers.
#
# This abort is made on a best-effort basis, meaning that the actual
# job thread will check once in a while to see whether an abort is
# requested and then stop processing by itself. There is no guarantee
# that the abort will stop the job any time soon or even at all.
def abort(self):
"""Aborts the processing of layers.
This abort is made on a best-effort basis, meaning that the actual
job thread will check once in a while to see whether an abort is
requested and then stop processing by itself. There is no guarantee
that the abort will stop the job any time soon or even at all.
"""
self._abort_requested = True
def setBuildPlate(self, new_value):

View file

@ -40,8 +40,9 @@ class StartJobResult(IntEnum):
ObjectsWithDisabledExtruder = 8
## Formatter class that handles token expansion in start/end gcode
class GcodeStartEndFormatter(Formatter):
"""Formatter class that handles token expansion in start/end gcode"""
def __init__(self, default_extruder_nr: int = -1) -> None:
super().__init__()
self._default_extruder_nr = default_extruder_nr
@ -82,8 +83,9 @@ class GcodeStartEndFormatter(Formatter):
return value
## Job class that builds up the message of scene data to send to CuraEngine.
class StartSliceJob(Job):
"""Job class that builds up the message of scene data to send to CuraEngine."""
def __init__(self, slice_message: Arcus.PythonMessage) -> None:
super().__init__()
@ -100,9 +102,11 @@ class StartSliceJob(Job):
def setBuildPlate(self, build_plate_number: int) -> None:
self._build_plate_number = build_plate_number
## Check if a stack has any errors.
## returns true if it has errors, false otherwise.
def _checkStackForErrors(self, stack: ContainerStack) -> bool:
"""Check if a stack has any errors."""
"""returns true if it has errors, false otherwise."""
if stack is None:
return False
@ -119,8 +123,9 @@ class StartSliceJob(Job):
Job.yieldThread()
return False
## Runs the job that initiates the slicing.
def run(self) -> None:
"""Runs the job that initiates the slicing."""
if self._build_plate_number is None:
self.setResult(StartJobResult.Error)
return
@ -323,14 +328,14 @@ class StartSliceJob(Job):
def setIsCancelled(self, value: bool):
self._is_cancelled = value
## Creates a dictionary of tokens to replace in g-code pieces.
#
# This indicates what should be replaced in the start and end g-codes.
# \param stack The stack to get the settings from to replace the tokens
# with.
# \return A dictionary of replacement tokens to the values they should be
# replaced with.
def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]:
"""Creates a dictionary of tokens to replace in g-code pieces.
This indicates what should be replaced in the start and end g-codes.
:param stack: The stack to get the settings from to replace the tokens with.
:return: A dictionary of replacement tokens to the values they should be replaced with.
"""
result = {}
for key in stack.getAllKeys():
value = stack.getProperty(key, "value")
@ -358,10 +363,12 @@ class StartSliceJob(Job):
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
## Replace setting tokens in a piece of g-code.
# \param value A piece of g-code to replace tokens in.
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
"""Replace setting tokens in a piece of g-code.
:param value: A piece of g-code to replace tokens in.
:param default_extruder_nr: Stack nr to use when no stack nr is specified, defaults to the global stack
"""
if not self._all_extruders_settings:
self._cacheAllExtruderSettings()
@ -377,8 +384,9 @@ class StartSliceJob(Job):
Logger.logException("w", "Unable to do token replacement on start/end g-code")
return str(value)
## Create extruder message from stack
def _buildExtruderMessage(self, stack: ContainerStack) -> None:
"""Create extruder message from stack"""
message = self._slice_message.addRepeatedMessage("extruders")
message.id = int(stack.getMetaDataEntry("position"))
if not self._all_extruders_settings:
@ -407,11 +415,13 @@ class StartSliceJob(Job):
setting.value = str(value).encode("utf-8")
Job.yieldThread()
## Sends all global settings to the engine.
#
# The settings are taken from the global stack. This does not include any
# per-extruder settings or per-object settings.
def _buildGlobalSettingsMessage(self, stack: ContainerStack) -> None:
"""Sends all global settings to the engine.
The settings are taken from the global stack. This does not include any
per-extruder settings or per-object settings.
"""
if not self._all_extruders_settings:
self._cacheAllExtruderSettings()
@ -445,15 +455,16 @@ class StartSliceJob(Job):
setting_message.value = str(value).encode("utf-8")
Job.yieldThread()
## Sends for some settings which extruder they should fallback to if not
# set.
#
# This is only set for settings that have the limit_to_extruder
# property.
#
# \param stack The global stack with all settings, from which to read the
# limit_to_extruder property.
def _buildGlobalInheritsStackMessage(self, stack: ContainerStack) -> None:
"""Sends for some settings which extruder they should fallback to if not set.
This is only set for settings that have the limit_to_extruder
property.
:param stack: The global stack with all settings, from which to read the
limit_to_extruder property.
"""
for key in stack.getAllKeys():
extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
if extruder_position >= 0: # Set to a specific extruder.
@ -462,10 +473,13 @@ class StartSliceJob(Job):
setting_extruder.extruder = extruder_position
Job.yieldThread()
## Check if a node has per object settings and ensure that they are set correctly in the message
# \param node Node to check.
# \param message object_lists message to put the per object settings in
def _handlePerObjectSettings(self, node: CuraSceneNode, message: Arcus.PythonMessage):
"""Check if a node has per object settings and ensure that they are set correctly in the message
:param node: Node to check.
:param message: object_lists message to put the per object settings in
"""
stack = node.callDecoration("getStack")
# Check if the node has a stack attached to it and the stack has any settings in the top container.
@ -501,10 +515,13 @@ class StartSliceJob(Job):
Job.yieldThread()
## Recursive function to put all settings that require each other for value changes in a list
# \param relations_set Set of keys of settings that are influenced
# \param relations list of relation objects that need to be checked.
def _addRelations(self, relations_set: Set[str], relations: List[SettingRelation]):
"""Recursive function to put all settings that require each other for value changes in a list
:param relations_set: Set of keys of settings that are influenced
:param relations: list of relation objects that need to be checked.
"""
for relation in filter(lambda r: r.role == "value" or r.role == "limit_to_extruder", relations):
if relation.type == RelationType.RequiresTarget:
continue