Merge remote-tracking branch 'origin/CURA-10717_Engine_plugin_orchestration' into CURA-10475_engineplugin

# Conflicts:
#	plugins/CuraEngineBackend/Cura.proto
#	plugins/CuraEngineBackend/StartSliceJob.py
This commit is contained in:
Jelle Spijker 2023-07-12 18:35:21 +02:00
commit 4aae50396b
No known key found for this signature in database
GPG key ID: 034D1C0527888B65
6 changed files with 247 additions and 120 deletions

View file

@ -5,17 +5,17 @@ body:
- type: markdown
attributes:
value: |
**Thank you for using Cura and wanting to report a bug.**
**Thank you for using Cura and wanting to report a bug. 🙏**
Before filing, please check if the issue already exists (either open or closed) by using the search bar on the issues page.
Before filing, [please check if the issue already exists](https://github.com/Ultimaker/Cura/issues?q=is%3Aissue) by using the search bar on the issues page.
If it does, comment there. Even if it's closed, we can reopen it based on your comment.
Also, please note the application version in the title of the issue "For example (5.3.1) Cannot connect to 3rd-party printer". Please do not write things like **Request** or **BUG** in the title, this is what labels are for.
Please include the cura version in the title of the issue. For example, *"[5.4.0] Support Brim is missing in this model"*.
- type: input
attributes:
label: Application Version
label: Cura Version
description: The version of Cura this issue occurs with.
placeholder: 5.3.0
placeholder: 5.4.0
validations:
required: true
- type: input
@ -28,14 +28,14 @@ body:
- type: input
attributes:
label: Printer
description: Which printer was selected in Cura?
placeholder: Ultimaker S7
description: Which printer was selected in Cura? It also helps to mention if you made any firmware modifications to your printer.
placeholder: Ultimaker S7 / Creality CR-10 with Klipper
validations:
required: true
- type: textarea
attributes:
label: Reproduction steps
description: Tell us what you did!
description: Share what you did, so we can reproduce it
placeholder: |
1. Something you did
2. Something you did next
@ -44,42 +44,39 @@ body:
- type: textarea
attributes:
label: Actual results
description: What happens after the above steps have been followed.
description: What happens after the above steps have been followed?
validations:
required: true
- type: textarea
attributes:
label: Expected results
description: What should happen after the above steps have been followed.
description: What should happen after the above steps have been followed?
validations:
required: true
- type: markdown
attributes:
value: |
Please be sure to add the following files:
* To save a project file go to File -> Save project.
Please make sure to .zip your project file.
For big files, you may need to use [WeTransfer](https://wetransfer.com/) or similar file-sharing sites.
G-code files are not project files!
Before you share, please think to yourself. Is this a model that can be shared?
* **Screenshots** of showing the problem, perhaps before/after images.
* A **log file** for crashes and similar issues.
### Please add the following files when they are related to...
* 🔵 **The quality of your print**
Please add **a Project File**. It contains the printer and settings we need for troubleshooting.
To save a project file go to File -> Save project.
Please make sure to .zip your project file. For big files, you may need to use [WeTransfer](https://wetransfer.com/) or similar file-sharing sites.
G-code files are not project files! Before you share, please think to yourself. Is this a model that can be shared?
![Alt Text](https://user-images.githubusercontent.com/40423138/240616958-5a9751f2-bd34-4808-9752-6fde2e27516e.gif)
* 🔵 **Using and interacting with Cura**
Please add **screenshots** showing the issue.
Before and after, and arrows can help here.
* 🔵 **Unexpected crashes and behavior**
Please add **a log file** with information on what your Cura is doing.
You can find your log file here:
Windows: `%APPDATA%\cura\<Cura version>\cura.log` or usually `C:\Users\\<your username>\AppData\Roaming\cura\<Cura version>\cura.log`
Windows: `%APPDATA%\cura\<Cura version>\cura.log`
MacOS: `$USER/Library/Application Support/cura/<Cura version>/cura.log`
Ubuntu/Linux: `$USER/.local/share/cura/<Cura version>/cura.log`
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
- type: checkboxes
attributes:
label: Checklist of files to include
options:
- label: Log file
- label: Project file
- type: textarea
attributes:
label: Additional information & file uploads
description: You can add these files and additional information that is relevant to the issue in the comments below.
label: Add your .zip and screenshots here ⬇️
description: You can add the zip file and additional information that is relevant to the issue in the comments below.
validations:
required: true

View file

@ -17,6 +17,13 @@ on:
- 'conandata.yml'
- 'GitVersion.yml'
- '*.jinja'
branches:
- '[1-9].[0-9]'
- '[1-9].[0-9][0-9]'
tags:
- '[1-9].[0-9].[0-9]*'
- '[1-9].[0-9].[0-9]'
- '[1-9].[0-9][0-9].[0-9]*'
jobs:
update-translations:

81
cura/BackendPlugin.py Normal file
View file

@ -0,0 +1,81 @@
# Copyright (c) 2023 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import subprocess
from typing import Optional, List
from UM.Logger import Logger
from UM.PluginObject import PluginObject
class BackendPlugin(PluginObject):
def __init__(self) -> None:
super().__init__()
self.__port: int = 0
self._plugin_address: str = "127.0.0.1"
self._plugin_command: Optional[List[str]] = None
self._process = None
self._is_running = False
self._supported_slots: List[int] = []
def getSupportedSlots(self) -> List[int]:
return self._supported_slots
def isRunning(self):
return self._is_running
def setPort(self, port: int) -> None:
self.__port = port
def getPort(self) -> int:
return self.__port
def getAddress(self) -> str:
return self._plugin_address
def _validatePluginCommand(self) -> list[str]:
"""
Validate the plugin command and add the port parameter if it is missing.
:return: A list of strings containing the validated plugin command.
"""
if not self._plugin_command or "--port" in self._plugin_command:
return self._plugin_command or []
return self._plugin_command + ["--port", str(self.__port)]
def start(self) -> bool:
"""
Starts the backend_plugin process.
:return: True if the plugin process started successfully, False otherwise.
"""
try:
# STDIN needs to be None because we provide no input, but communicate via a local socket instead.
# The NUL device sometimes doesn't exist on some computers.
self._process = subprocess.Popen(self._validatePluginCommand(), stdin = None)
self._is_running = True
return True
except PermissionError:
Logger.log("e", f"Couldn't start backend_plugin [{self._plugin_id}]: No permission to execute process.")
except FileNotFoundError:
Logger.logException("e", f"Unable to find backend_plugin executable [{self._plugin_id}]")
except BlockingIOError:
Logger.logException("e", f"Couldn't start backend_plugin [{self._plugin_id}]: Resource is temporarily unavailable")
except OSError as e:
Logger.logException("e", f"Couldn't start backend_plugin [{self._plugin_id}]: Operating system is blocking it (antivirus?)")
return False
def stop(self) -> bool:
if not self._process:
self._is_running = False
return True # Nothing to stop
try:
self._process.terminate()
return_code = self._process.wait()
self._is_running = False
Logger.log("d", f"Backend_plugin [{self._plugin_id}] was killed. Received return code {return_code}")
return True
except PermissionError:
Logger.log("e", "Unable to kill running engine. Access is denied.")
return False

View file

@ -205,6 +205,8 @@ class CuraApplication(QtApplication):
self._cura_scene_controller = None
self._machine_error_checker = None
self._backend_plugins: List[BackendPlugin] = []
self._machine_settings_manager = MachineSettingsManager(self, parent = self)
self._material_management_model = None
self._quality_management_model = None
@ -792,6 +794,7 @@ class CuraApplication(QtApplication):
self._plugin_registry.addType("profile_reader", self._addProfileReader)
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
self._plugin_registry.addType("backend_plugin", self._addBackendPlugin)
if Platform.isLinux():
lib_suffixes = {"", "64", "32", "x32"} # A few common ones on different distributions.
@ -1730,6 +1733,12 @@ class CuraApplication(QtApplication):
def _addProfileWriter(self, profile_writer):
pass
def _addBackendPlugin(self, backend_plugin: "BackendPlugin") -> None:
self._backend_plugins.append(backend_plugin)
def getBackendPlugins(self) -> List["BackendPlugin"]:
return self._backend_plugins
@pyqtSlot("QSize")
def setMinimumWindowSize(self, size):
main_window = self.getMainWindow()

View file

@ -46,6 +46,19 @@ catalog = i18nCatalog("cura")
class CuraEngineBackend(QObject, Backend):
backendError = Signal()
printDurationMessage = Signal()
"""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."""
slicingCancelled = Signal()
"""Emitted when the slicing process is aborted forcefully."""
def __init__(self) -> None:
"""Starts the back-end plug-in.
@ -70,7 +83,7 @@ class CuraEngineBackend(QObject, Backend):
os.path.join(CuraApplication.getInstallPrefix(), "bin"),
os.path.dirname(os.path.abspath(sys.executable)),
]
self._last_backend_plugin_port = self._port + 1000
for path in search_path:
engine_path = os.path.join(path, executable_name)
if os.path.isfile(engine_path):
@ -86,9 +99,9 @@ class CuraEngineBackend(QObject, Backend):
self._default_engine_location = execpath
break
application = CuraApplication.getInstance() #type: CuraApplication
self._multi_build_plate_model = None #type: Optional[MultiBuildPlateModel]
self._machine_error_checker = None #type: Optional[MachineErrorChecker]
application: CuraApplication = CuraApplication.getInstance()
self._multi_build_plate_model: Optional[MultiBuildPlateModel] = None
self._machine_error_checker: Optional[MachineErrorChecker] = None
if not self._default_engine_location:
raise EnvironmentError("Could not find CuraEngine")
@ -99,13 +112,15 @@ class CuraEngineBackend(QObject, Backend):
application.getPreferences().addPreference("backend/location", self._default_engine_location)
# Workaround to disable layer view processing if layer view is not active.
self._layer_view_active = False #type: bool
self._layer_view_active: bool = False
self._onActiveViewChanged()
self._stored_layer_data = [] # type: List[Arcus.PythonMessage]
self._stored_optimized_layer_data = {} # type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._stored_layer_data: List[Arcus.PythonMessage] = []
self._scene = application.getController().getScene() #type: Scene
# key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
self._stored_optimized_layer_data: Dict[int, List[Arcus.PythonMessage]] = {}
self._scene: Scene = application.getController().getScene()
self._scene.sceneChanged.connect(self._onSceneChanged)
# Triggers for auto-slicing. Auto-slicing is triggered as follows:
@ -116,7 +131,7 @@ class CuraEngineBackend(QObject, Backend):
# If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
# to start the auto-slicing timer again.
#
self._global_container_stack = None #type: Optional[ContainerStack]
self._global_container_stack: Optional[ContainerStack] = None
# Listeners for receiving messages from the back-end.
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
@ -128,31 +143,34 @@ class CuraEngineBackend(QObject, Backend):
self._message_handlers["cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
self._start_slice_job = None #type: Optional[StartSliceJob]
self._start_slice_job_build_plate = None #type: Optional[int]
self._slicing = False #type: bool # Are we currently slicing?
self._restart = False #type: bool # Back-end is currently restarting?
self._tool_active = False #type: bool # If a tool is active, some tasks do not have to do anything
self._always_restart = True #type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
self._process_layers_job = None #type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers.
self._build_plates_to_be_sliced = [] #type: List[int] # what needs slicing?
self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not?
self._start_slice_job: Optional[StartSliceJob] = None
self._start_slice_job_build_plate: Optional[int] = None
self._slicing: bool = False # Are we currently slicing?
self._restart: bool = False # Back-end is currently restarting?
self._tool_active: bool = False # If a tool is active, some tasks do not have to do anything
self._always_restart: bool = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
self._process_layers_job: Optional[ProcessSlicedLayersJob] = None # The currently active job to process layers, or None if it is not processing layers.
self._build_plates_to_be_sliced: List[int] = [] # what needs slicing?
self._engine_is_fresh: bool = True # Is the newly started engine used before or not?
self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer
self._error_message = None #type: Optional[Message] # Pop-up message that shows errors.
self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed
self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool)
self._backend_log_max_lines: int = 20000 # Maximum number of lines to buffer
self._error_message: Optional[Message] = None # Pop-up message that shows errors.
self._time_start_process = None #type: Optional[float]
self._is_disabled = False #type: bool
# Count number of objects to see if there is something changed
self._last_num_objects: Dict[int, int] = defaultdict(int)
self._postponed_scene_change_sources: List[SceneNode] = [] # scene change is postponed (by a tool)
self._time_start_process: Optional[float] = None
self._is_disabled: bool = False
application.getPreferences().addPreference("general/auto_slice", False)
self._use_timer = False #type: bool
# When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
# This timer will group them up, and only slice for the last setting changed signal.
self._use_timer: bool = False
# When you update a setting and other settings get changed through inheritance, many propertyChanged
# signals are fired. This timer will group them up, and only slice for the last setting changed signal.
# TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
self._change_timer = QTimer() #type: QTimer
self._change_timer: QTimer = QTimer()
self._change_timer.setSingleShot(True)
self._change_timer.setInterval(500)
self.determineAutoSlicing()
@ -172,10 +190,25 @@ class CuraEngineBackend(QObject, Backend):
self._slicing_error_message.actionTriggered.connect(self._reportBackendError)
self._resetLastSliceTimeStats()
self._snapshot = None #type: Optional[QImage]
self._snapshot: Optional[QImage] = None
application.initializationFinished.connect(self.initialize)
def startPlugins(self) -> None:
"""
Ensure that all backend plugins are started
It assigns unique ports to each plugin to avoid conflicts.
:return:
"""
backend_plugins = CuraApplication.getInstance().getBackendPlugins()
for backend_plugin in backend_plugins:
if backend_plugin.isRunning():
continue
# Set the port to prevent plugins from using the same one.
backend_plugin.setPort(self._last_backend_plugin_port)
self._last_backend_plugin_port += 1
backend_plugin.start()
def _resetLastSliceTimeStats(self) -> None:
self._time_start_process = None
self._time_send_message = None
@ -202,7 +235,8 @@ class CuraEngineBackend(QObject, Backend):
application.getMachineManager().globalContainerChanged.connect(self._onGlobalStackChanged)
self._onGlobalStackChanged()
# extruder enable / disable. Actually wanted to use machine manager here, but the initialization order causes it to crash
# 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)
@ -239,26 +273,14 @@ class CuraEngineBackend(QObject, Backend):
command += ["connect", "127.0.0.1:{0}".format(self._port), ""]
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.")
parser.add_argument("--debug", action = "store_true", default = False,
help = "Turn on the debug mode by setting this option.")
known_args = vars(parser.parse_known_args()[0])
if known_args["debug"]:
command.append("-vvv")
return command
printDurationMessage = Signal()
"""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."""
slicingCancelled = Signal()
"""Emitted when the slicing process is aborted forcefully."""
@pyqtSlot()
def stopSlicing(self) -> None:
self.setState(BackendState.NotStarted)
@ -266,7 +288,8 @@ class CuraEngineBackend(QObject, Backend):
self._terminate()
self._createSocket()
if self._process_layers_job is not None: # We were processing layers. Stop that, the layers are going to change soon.
if self._process_layers_job is not None:
# We were processing layers. Stop that, the layers are going to change soon.
Logger.log("i", "Aborting process layers job...")
self._process_layers_job.abort()
self._process_layers_job = None
@ -281,7 +304,7 @@ class CuraEngineBackend(QObject, Backend):
self.markSliceAll()
self.slice()
@call_on_qt_thread # must be called from the main thread because of OpenGL
@call_on_qt_thread # Must be called from the main thread because of OpenGL
def _createSnapshot(self) -> None:
self._snapshot = None
if not CuraApplication.getInstance().isVisible:
@ -290,7 +313,7 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("i", "Creating thumbnail image (just before slice)...")
try:
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
except:
except Exception:
Logger.logException("w", "Failed to create snapshot image")
self._snapshot = None # Failing to create thumbnail should not fail creation of UFP
@ -302,6 +325,8 @@ class CuraEngineBackend(QObject, Backend):
self._createSnapshot()
self.startPlugins()
Logger.log("i", "Starting to slice...")
self._time_start_process = time()
if not self._build_plates_to_be_sliced:
@ -315,7 +340,8 @@ class CuraEngineBackend(QObject, Backend):
return
if not hasattr(self._scene, "gcode_dict"):
self._scene.gcode_dict = {} #type: ignore #Because we are creating the missing attribute here.
self._scene.gcode_dict = {} # type: ignore
# We need to ignore type because we are creating the missing attribute here.
# see if we really have to slice
application = CuraApplication.getInstance()
@ -326,9 +352,9 @@ class CuraEngineBackend(QObject, Backend):
self._stored_layer_data = []
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #Because we created this attribute above.
self._scene.gcode_dict[build_plate_to_be_sliced] = [] # type: ignore
# We need to ignore the type because we created this attribute above.
Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
if self._build_plates_to_be_sliced:
self.slice()
@ -345,7 +371,7 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #[] indexed by build plate number
self._scene.gcode_dict[build_plate_to_be_sliced] = [] # type: ignore #[] indexed by build plate number
self._slicing = True
self.slicingStarted.emit()
@ -384,7 +410,8 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("d", "Engine process is killed. Received return code %s", self._process.wait()) # type: ignore
self._process = None # type: ignore
except Exception as e: # terminating a process that is already terminating causes an exception, silently ignore this.
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))
def _onStartSliceCompleted(self, job: StartSliceJob) -> None:
@ -429,7 +456,7 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
return
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
error_keys = [] #type: List[str]
error_keys: List[str] = []
for extruder in extruders:
error_keys.extend(extruder.getErrorKeys())
if not extruders:
@ -524,7 +551,7 @@ class CuraEngineBackend(QObject, Backend):
# Preparation completed, send it to the backend.
self._socket.sendMessage(job.getSliceMessage())
# Notify the user that it's now up to the backend to do it's job
# Notify the user that it's now up to the backend to do its job
self.setState(BackendState.Processing)
# Handle time reporting.
@ -551,7 +578,8 @@ class CuraEngineBackend(QObject, Backend):
self._is_disabled = True
gcode_list = node.callDecoration("getGCodeList")
if gcode_list is not None:
self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list #type: ignore #Because we generate this attribute dynamically.
self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list # type: ignore
# We need to ignore type because we generate this attribute dynamically.
if self._use_timer == enable_timer:
return self._use_timer
@ -566,7 +594,7 @@ class CuraEngineBackend(QObject, Backend):
def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
"""Return a dict with number of objects per build plate"""
num_objects = defaultdict(int) #type: Dict[int, int]
num_objects: Dict[int, int] = defaultdict(int)
for node in DepthFirstIterator(self._scene.getRoot()):
# Only count sliceable objects
if node.callDecoration("isSliceable"):
@ -646,11 +674,13 @@ class CuraEngineBackend(QObject, Backend):
self._terminate()
self._createSocket()
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError, Arcus.ErrorCode.ConnectionResetError, Arcus.ErrorCode.Debug]:
if error.getErrorCode() not in [Arcus.ErrorCode.BindFailedError,
Arcus.ErrorCode.ConnectionResetError,
Arcus.ErrorCode.Debug]:
Logger.log("w", "A socket error caused the connection to be reset")
# _terminate()' function sets the job status to 'cancel', after reconnecting to another Port the job status
# needs to be updated. Otherwise backendState is "Unable To Slice"
# needs to be updated. Otherwise, backendState is "Unable To Slice"
if error.getErrorCode() == Arcus.ErrorCode.BindFailedError and self._start_slice_job is not None:
self._start_slice_job.setIsCancelled(False)
@ -672,7 +702,7 @@ class CuraEngineBackend(QObject, Backend):
for node in DepthFirstIterator(self._scene.getRoot()):
if node.callDecoration("getLayerData"):
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
# We can assume that all nodes have a parent as we're looping through the scene (and filter out root)
# We can assume that all nodes have a parent as we're looping through the scene and filter out root
cast(SceneNode, node.getParent()).removeChild(node)
def markSliceAll(self) -> None:
@ -701,7 +731,7 @@ class CuraEngineBackend(QObject, Backend):
: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.
if property == "value": # Only re-slice if the value has changed.
self.needsSlicing()
self._onChanged()
@ -770,8 +800,10 @@ class CuraEngineBackend(QObject, Backend):
self._time_end_slice = time()
try:
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #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.
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore
# We need to ignore the type because it was generated dynamically.
except KeyError:
# Can occur if the g-code has been cleared while a slice message is still arriving from the other end.
gcode_list = []
application = CuraApplication.getInstance()
for index, line in enumerate(gcode_list):
@ -816,7 +848,8 @@ class CuraEngineBackend(QObject, Backend):
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.
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.
def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None:
@ -828,7 +861,8 @@ class CuraEngineBackend(QObject, Backend):
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.
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.
def _onSliceUUIDMessage(self, message: Arcus.PythonMessage) -> None:
@ -955,7 +989,8 @@ class CuraEngineBackend(QObject, Backend):
view = CuraApplication.getInstance().getController().getActiveView()
if view:
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
if view.getPluginId() == "SimulationView":
# If switching to layer view, we should process the layers if that hasn't been done yet.
self._layer_view_active = True
# There is data and we're not slicing at the moment
# if we are slicing, there is no need to re-calculate the data as it will be invalid in a moment.
@ -1007,7 +1042,8 @@ class CuraEngineBackend(QObject, Backend):
self._global_container_stack = CuraApplication.getInstance().getMachineManager().activeMachine
if self._global_container_stack:
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
# Note: Only starts slicing when the value changed.
self._global_container_stack.propertyChanged.connect(self._onSettingChanged)
self._global_container_stack.containersChanged.connect(self._onChanged)
for extruder in self._global_container_stack.extruderList:

View file

@ -302,21 +302,18 @@ class StartSliceJob(Job):
for extruder_stack in global_stack.extruderList:
self._buildExtruderMessage(extruder_stack)
# EnginePlugins
# TODO: don't hardcode them
# Ports: are chosen based on https://stackoverflow.com/questions/10476987/best-tcp-port-number-range-for-internal-applications
plugins = {
0: {"address": os.environ.get("SIMPLIFY_ADDRESS", "localhost"), "port": os.environ.get("SIMPLIFY_PORT", 33700)} if os.environ.get("SIMPLIFY_ENABLE") is not None else None,
1: {"address": os.environ.get("POSTPROCESS_ADDRESS", "localhost"), "port": os.environ.get("POSTPROCESS_PORT", 33701)} if os.environ.get("POSTPROCESS_ENABLE") is not None else None,
}
for plugin, connection in plugins.items():
for plugin in CuraApplication.getInstance().getBackendPlugins():
for slot in plugin.getSupportedSlots():
# Right now we just send the message for every slot that we support. A single plugin can support
# multiple slots
# In the future the frontend will need to decide what slots that a plugin actually supports should
# also be used. For instance, if you have two plugins and each of them support a_generate and b_generate
# only one of each can actually be used (eg; plugin 1 does both, plugin 1 does a_generate and 2 does
# b_generate, etc).
plugin_message = self._slice_message.addRepeatedMessage("engine_plugins")
plugin_message.id = plugin
if connection:
plugin_message.address = connection["address"]
plugin_message.port = connection["port"]
plugin_message.id = slot
plugin_message.address = plugin.getAddress()
plugin_message.port = plugin.getPort()
for group in filtered_object_groups:
group_message = self._slice_message.addRepeatedMessage("object_lists")