diff --git a/CHANGELOG.md b/CHANGELOG.md index be61ba2..835d438 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog of Cura-DuetRRFPlugin +## v1.2.8: 2022-XX-XX +* also embed QOI thumbnail images when using "Save to Disk" option + ## v1.2.7: 2022-04-26 * bump compatibility for Cura 5.0 / API 8.0, while retaining compatibility with Cura 4.11 / 7.7 and up diff --git a/DuetRRFOutputDevice.py b/DuetRRFOutputDevice.py index c4ba7f3..acc5f4d 100644 --- a/DuetRRFOutputDevice.py +++ b/DuetRRFOutputDevice.py @@ -22,16 +22,13 @@ from UM.Application import Application from UM.Logger import Logger from UM.Message import Message -from UM.Mesh.MeshWriter import MeshWriter -from UM.PluginRegistry import PluginRegistry from UM.OutputDevice.OutputDevice import OutputDevice from UM.OutputDevice import OutputDeviceError from UM.i18n import i18nCatalog catalog = i18nCatalog("cura") from . import DuetRRFSettings -from .thumbnails import generate_thumbnail - +from .helpers import serializing_scene_to_gcode class OutputStage(Enum): ready = 0 @@ -229,17 +226,7 @@ def _onFilenameAccepted(self): ) self._message.show() - # get the gcode through the GCodeWrite plugin - # this serializes the actual scene and should produce the same output as "Save to File" - gcode_stream = self._serializing_scene_to_gcode() - - # generate model thumbnail and embedd in gcode file - self._message.setText("Rendering thumbnail image...") - thumbnail_stream = generate_thumbnail() - - # assemble everything and inject custom data - self._message.setText("Assembling final gcode file...") - self._stream = self._assemble_final_gcode(gcode_stream, thumbnail_stream) + self._stream = serializing_scene_to_gcode() # start upload workflow self._message.setText("Uploading {} ...".format(self._fileName)) @@ -250,30 +237,6 @@ def _onFilenameAccepted(self): on_error=self._check_duet3_sbc, ) - def _serializing_scene_to_gcode(self): - Logger.log("d", "Serializing gcode...") - gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter")) - gcode_stream = StringIO() - success = gcode_writer.write(gcode_stream, None) - if not success: - Logger.log("e", "GCodeWriter failed.") - return None - return gcode_stream - - def _assemble_final_gcode(self, gcode_stream, thumbnail_stream): - Logger.log("d", "Assembling final gcode file...") - - final_stream = StringIO() - gcode_stream.seek(0) - for l in gcode_stream.readlines(): - final_stream.write(l) - if l.startswith(";Generated with"): - version = DuetRRFSettings.get_plugin_version() - final_stream.write(f";Exported with Cura-DuetRRF v{version} plugin by Thomas Kriechbaumer\n") - final_stream.write(thumbnail_stream.getvalue()) - - return final_stream - def _check_duet3_sbc(self, reply, error): Logger.log("d", "rr_connect failed with error " + str(error)) if error == QNetworkReply.NetworkError.ContentNotFoundError: diff --git a/DuetRRFPlugin.py b/DuetRRFPlugin.py index 71cd065..1f4f43e 100644 --- a/DuetRRFPlugin.py +++ b/DuetRRFPlugin.py @@ -1,3 +1,4 @@ +from io import StringIO import json try: # Cura 5 @@ -8,6 +9,7 @@ from cura.CuraApplication import CuraApplication from cura.Settings.CuraContainerRegistry import CuraContainerRegistry +from UM.Application import Application from UM.Message import Message from UM.Logger import Logger from UM.Extension import Extension @@ -17,8 +19,8 @@ catalog = i18nCatalog("cura") from .DuetRRFOutputDevice import DuetRRFConfigureOutputDevice, DuetRRFOutputDevice, DuetRRFDeviceType -from .DuetRRFSettings import delete_config, get_config, init_settings, DUETRRF_SETTINGS - +from .DuetRRFSettings import get_plugin_version, delete_config, get_config, init_settings, DUETRRF_SETTINGS +from .thumbnails import generate_thumbnail class DuetRRFPlugin(Extension, OutputDevicePlugin): def __init__(self): @@ -26,6 +28,7 @@ def __init__(self): self._application = CuraApplication.getInstance() self._application.globalContainerStackChanged.connect(self._checkDuetRRFOutputDevices) self._application.initializationFinished.connect(self._delay_check_unmapped_settings) + self._application.getOutputDeviceManager().writeStarted.connect(self._embed_thumbnails) init_settings() @@ -39,6 +42,32 @@ def start(self): def stop(self, store_data: bool = True): pass + def _embed_thumbnails(self, output_device) -> None: + # fetch sliced gcode from scene and active build plate + active_build_plate_id = self._application.getMultiBuildPlateModel().activeBuildPlate + scene = Application.getInstance().getController().getScene() + gcode_dict = getattr(scene, "gcode_dict", None) + if not gcode_dict: + return + gcode_list = gcode_dict[active_build_plate_id] + if not gcode_list: + return + + if ";Exported with Cura-DuetRRF" not in gcode_list[0]: + # assemble everything and inject custom data + Logger.log("i", "Assembling final gcode file...") + + version = get_plugin_version() + thumbnail_stream = generate_thumbnail() + gcode_list[0] += f";Exported with Cura-DuetRRF v{version} plugin by Thomas Kriechbaumer\n" + gcode_list[0] += thumbnail_stream.getvalue() + + # store new gcode back into scene and active build plate + gcode_dict[active_build_plate_id] = gcode_list + setattr(scene, "gcode_dict", gcode_dict) + else: + Logger.log("e", "Already embedded thumbnails") + def _delay_check_unmapped_settings(self): self._change_timer = QTimer() self._change_timer.setInterval(10000) diff --git a/README.md b/README.md index 0207837..3a99bdb 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ This button is also a dropdown to choose between **Print**, **Simulate**, or * Works with HTTP Basic Auth (optional) * Works with RRF passwords (if you used `M551`, default is `reprap`) * No support for UNC paths, only IP addresses or resolvable domain names (DNS) +* Embeds thumbnails in QOI format for PanelDue and DWC ## Use diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..39a35bd --- /dev/null +++ b/helpers.py @@ -0,0 +1,20 @@ +from io import StringIO +from typing import cast + +from UM.Logger import Logger +from UM.Mesh.MeshWriter import MeshWriter +from UM.PluginRegistry import PluginRegistry + + +def serializing_scene_to_gcode(): + # get the gcode through the GCodeWrite plugin + # this serializes the actual scene and should produce the same output as "Save to File" + + Logger.log("d", "Serializing gcode...") + gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter")) + gcode_stream = StringIO() + success = gcode_writer.write(gcode_stream, None) + if not success: + Logger.log("e", "GCodeWriter failed.") + return None + return gcode_stream