mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-16 11:17:49 -06:00
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:
commit
4aae50396b
6 changed files with 247 additions and 120 deletions
55
.github/ISSUE_TEMPLATE/bugreport.yaml
vendored
55
.github/ISSUE_TEMPLATE/bugreport.yaml
vendored
|
@ -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?
|
||||

|
||||
* 🔵 **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
|
||||
|
||||
|
|
7
.github/workflows/update-translation.yml
vendored
7
.github/workflows/update-translation.yml
vendored
|
@ -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
81
cura/BackendPlugin.py
Normal 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
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue