Browse Source

Use preferred output writer

This introduces a greenlet to allow optional interrupting of a function for waiting on the selectPrinter function.

Contributes to issue CURA-5097.
Ghostkeeper 7 years ago
parent
commit
5bb20f6133
1 changed files with 83 additions and 26 deletions
  1. 83 26
      plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py

+ 83 - 26
plugins/UM3NetworkPrinting/ClusterUM3OutputDevice.py

@@ -1,12 +1,16 @@
-# Copyright (c) 2017 Ultimaker B.V.
+# Copyright (c) 2018 Ultimaker B.V.
 # Cura is released under the terms of the LGPLv3 or higher.
 
+from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary).
+from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously.
 from UM.Logger import Logger
 from UM.Application import Application
 from UM.Settings.ContainerRegistry import ContainerRegistry
 from UM.i18n import i18nCatalog
 from UM.Message import Message
 from UM.Qt.Duration import Duration, DurationFormat
+from UM.OutputDevice import OutputDeviceError #To show that something went wrong when writing.
+from UM.Scene.SceneNode import SceneNode #For typing.
 
 from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
 from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
@@ -20,10 +24,11 @@ from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
 from PyQt5.QtGui import QDesktopServices
 from PyQt5.QtCore import pyqtSlot, QUrl, pyqtSignal, pyqtProperty, QObject
 
-from time import time
+from time import time, sleep
 from datetime import datetime
 from typing import Optional, Dict, List
 
+import io #To create the correct buffers for sending data to the printer.
 import json
 import os
 
@@ -79,24 +84,44 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
 
         self._latest_reply_handler = None
 
-    def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
+    def requestWrite(self, nodes: List[SceneNode], file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
         self.writeStarted.emit(self)
 
-        gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
-        active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
-        gcode_list = gcode_dict[active_build_plate_id]
+        #Formats supported by this application (file types that we can actually write).
+        if file_handler:
+            file_formats = file_handler.getSupportedFileTypesWrite()
+        else:
+            file_formats = Application.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
 
-        if not gcode_list:
-            # Unable to find g-code. Nothing to send
-            return
+        #Create a list from the supported file formats string.
+        container = Application.getInstance().getGlobalContainerStack().findContainer({"file_formats": "*"})
+        machine_file_formats = [file_type.strip() for file_type in container.getMetaDataEntry("file_formats").split(";")]
 
-        self._gcode = gcode_list
+        # Take the intersection between file_formats and machine_file_formats.
+        format_by_mimetype = {format["mime_type"]: format for format in file_formats}
+        file_formats = [format_by_mimetype[mimetype] for mimetype in machine_file_formats] #Keep them ordered according to the preference in machine_file_formats.
 
-        is_job_sent = True
-        if len(self._printers) > 1:
-            self._spawnPrinterSelectionDialog()
+        if len(file_formats) == 0:
+            Logger.log("e", "There are no file formats available to write with!")
+            raise OutputDeviceError.WriteRequestFailedError(i18n_catalog.i18nc("@info:status", "There are no file formats available to write with!"))
+        preferred_format = file_formats[0]
+
+        #Just take the first file format available.
+        if file_handler is not None:
+            writer = file_handler.getWriterByMimeType(preferred_format["mime_type"])
         else:
-            is_job_sent = self.sendPrintJob()
+            writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(preferred_format["mime_type"])
+
+        #This function pauses with the yield, waiting on instructions on which printer it needs to print with.
+        self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
+        self._sending_job.send(None) #Start the generator.
+
+        if len(self._printers) > 1: #We need to ask the user.
+            self._spawnPrinterSelectionDialog()
+            is_job_sent = True
+        else: #Just immediately continue.
+            self._sending_job.send("") #No specifically selected printer.
+            is_job_sent = self._sending_job.send(None)
 
         # Notify the UI that a switch to the print monitor should happen
         if is_job_sent:
@@ -113,29 +138,54 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
     def clusterSize(self):
         return self._cluster_size
 
-    @pyqtSlot()
+    ##  Allows the user to choose a printer to print with from the printer
+    #   selection dialogue.
+    #   \param target_printer The name of the printer to target.
     @pyqtSlot(str)
-    def sendPrintJob(self, target_printer: str = ""):
+    def selectPrinter(self, target_printer: str = "") -> None:
+        self._sending_job.send(target_printer)
+
+    ##  Greenlet to send a job to the printer over the network.
+    #
+    #   This greenlet gets called asynchronously in requestWrite. It is a
+    #   greenlet in order to optionally wait for selectPrinter() to select a
+    #   printer.
+    #   The greenlet yields exactly three times: First time None,
+    #   \param writer The file writer to use to create the data.
+    #   \param preferred_format A dictionary containing some information about
+    #   what format to write to. This is necessary to create the correct buffer
+    #   types and file extension and such.
+    def _sendPrintJob(self, writer: FileWriter, preferred_format: Dict, nodes: List[SceneNode]):
         Logger.log("i", "Sending print job to printer.")
         if self._sending_gcode:
             self._error_message = Message(
                 i18n_catalog.i18nc("@info:status",
                                    "Sending new jobs (temporarily) blocked, still sending the previous print job."))
             self._error_message.show()
-            return False
+            yield #Wait on the user to select a target printer.
+            yield #Wait for the write job to be finished.
+            yield False #Return whether this was a success or not.
+            yield #Prevent StopIteration.
 
         self._sending_gcode = True
 
-        self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), 0, False, -1,
-                                         i18n_catalog.i18nc("@info:title", "Sending Data"))
-        self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), None, "")
+        target_printer = yield #Potentially wait on the user to select a target printer.
+
+        # Using buffering greatly reduces the write time for many lines of gcode
+        if preferred_format["mode"] == FileWriter.OutputMode.TextMode:
+            stream = io.StringIO()
+        else: #Binary mode.
+            stream = io.BytesIO()
+
+        job = WriteFileJob(writer, stream, nodes, preferred_format["mode"])
+
+        self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1,
+                                         title = i18n_catalog.i18nc("@info:title", "Sending Data"))
+        self._progress_message.addAction("Abort", i18n_catalog.i18nc("@action:button", "Cancel"), icon = None, description = "")
         self._progress_message.actionTriggered.connect(self._progressMessageActionTriggered)
         self._progress_message.show()
 
-        compressed_gcode = self._compressGCode()
-        if compressed_gcode is None:
-            # Abort was called.
-            return False
+        job.start()
 
         parts = []
 
@@ -149,11 +199,18 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
 
         file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
 
-        parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, compressed_gcode))
+        while not job.isFinished():
+            sleep(0.1)
+        output = stream.getvalue() #Either str or bytes depending on the output mode.
+        if isinstance(stream, io.StringIO):
+            output = output.encode("utf-8")
+
+        parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, output))
 
         self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress)
 
-        return True
+        yield True #Return that we had success!
+        yield #To prevent having to catch the StopIteration exception.
 
     @pyqtProperty(QObject, notify=activePrinterChanged)
     def activePrinter(self) -> Optional[PrinterOutputModel]: