RemovableDriveOutputDevice.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. import os.path
  2. from UM.Application import Application
  3. from UM.Logger import Logger
  4. from UM.Message import Message
  5. from UM.Mesh.WriteMeshJob import WriteMeshJob
  6. from UM.Mesh.MeshWriter import MeshWriter
  7. from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
  8. from UM.OutputDevice.OutputDevice import OutputDevice
  9. from UM.OutputDevice import OutputDeviceError
  10. from UM.i18n import i18nCatalog
  11. catalog = i18nCatalog("cura")
  12. class RemovableDriveOutputDevice(OutputDevice):
  13. def __init__(self, device_id, device_name):
  14. super().__init__(device_id)
  15. self.setName(device_name)
  16. self.setShortDescription(catalog.i18nc("@action:button", "Save to Removable Drive"))
  17. self.setDescription(catalog.i18nc("@item:inlistbox", "Save to Removable Drive {0}").format(device_name))
  18. self.setIconName("save_sd")
  19. self.setPriority(1)
  20. self._writing = False
  21. self._stream = None
  22. def requestWrite(self, node, file_name = None, filter_by_machine = False):
  23. filter_by_machine = True # This plugin is indended to be used by machine (regardless of what it was told to do)
  24. if self._writing:
  25. raise OutputDeviceError.DeviceBusyError()
  26. # Formats supported by this application (File types that we can actually write)
  27. file_formats = Application.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
  28. if filter_by_machine:
  29. container = Application.getInstance().getGlobalContainerStack().findContainer({"file_formats": "*"})
  30. # Create a list from supported file formats string
  31. machine_file_formats = [file_type.strip() for file_type in container.getMetaDataEntry("file_formats").split(";")]
  32. # Take the intersection between file_formats and machine_file_formats.
  33. file_formats = list(filter(lambda file_format: file_format["mime_type"] in machine_file_formats, file_formats))
  34. if len(file_formats) == 0:
  35. Logger.log("e", "There are no file formats available to write with!")
  36. raise OutputDeviceError.WriteRequestFailedError()
  37. # Just take the first file format available.
  38. writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(file_formats[0]["mime_type"])
  39. extension = file_formats[0]["extension"]
  40. if file_name is None:
  41. for n in BreadthFirstIterator(node):
  42. if n.getMeshData():
  43. file_name = n.getName()
  44. if file_name:
  45. break
  46. if not file_name:
  47. Logger.log("e", "Could not determine a proper file name when trying to write to %s, aborting", self.getName())
  48. raise OutputDeviceError.WriteRequestFailedError()
  49. if extension: # Not empty string.
  50. extension = "." + extension
  51. file_name = os.path.join(self.getId(), os.path.splitext(file_name)[0] + extension)
  52. try:
  53. Logger.log("d", "Writing to %s", file_name)
  54. # Using buffering greatly reduces the write time for many lines of gcode
  55. self._stream = open(file_name, "wt", buffering = 1, encoding = "utf-8")
  56. job = WriteMeshJob(writer, self._stream, node, MeshWriter.OutputMode.TextMode)
  57. job.setFileName(file_name)
  58. job.progress.connect(self._onProgress)
  59. job.finished.connect(self._onFinished)
  60. message = Message(catalog.i18nc("@info:progress", "Saving to Removable Drive <filename>{0}</filename>").format(self.getName()), 0, False, -1)
  61. message.show()
  62. self.writeStarted.emit(self)
  63. job._message = message
  64. self._writing = True
  65. job.start()
  66. except PermissionError as e:
  67. Logger.log("e", "Permission denied when trying to write to %s: %s", file_name, str(e))
  68. raise OutputDeviceError.PermissionDeniedError(catalog.i18nc("@info:status", "Could not save to <filename>{0}</filename>: <message>{1}</message>").format(file_name, str(e))) from e
  69. except OSError as e:
  70. Logger.log("e", "Operating system would not let us write to %s: %s", file_name, str(e))
  71. raise OutputDeviceError.WriteRequestFailedError(catalog.i18nc("@info:status", "Could not save to <filename>{0}</filename>: <message>{1}</message>").format(file_name, str(e))) from e
  72. def _onProgress(self, job, progress):
  73. if hasattr(job, "_message"):
  74. job._message.setProgress(progress)
  75. self.writeProgress.emit(self, progress)
  76. def _onFinished(self, job):
  77. if self._stream:
  78. # Explicitly closing the stream flushes the write-buffer
  79. self._stream.close()
  80. self._stream = None
  81. if hasattr(job, "_message"):
  82. job._message.hide()
  83. job._message = None
  84. self._writing = False
  85. self.writeFinished.emit(self)
  86. if job.getResult():
  87. message = Message(catalog.i18nc("@info:status", "Saved to Removable Drive {0} as {1}").format(self.getName(), os.path.basename(job.getFileName())))
  88. message.addAction("eject", catalog.i18nc("@action:button", "Eject"), "eject", catalog.i18nc("@action", "Eject removable device {0}").format(self.getName()))
  89. message.actionTriggered.connect(self._onActionTriggered)
  90. message.show()
  91. self.writeSuccess.emit(self)
  92. else:
  93. message = Message(catalog.i18nc("@info:status", "Could not save to removable drive {0}: {1}").format(self.getName(), str(job.getError())))
  94. message.show()
  95. self.writeError.emit(self)
  96. job.getStream().close()
  97. def _onActionTriggered(self, message, action):
  98. if action == "eject":
  99. if Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("RemovableDriveOutputDevice").ejectDevice(self):
  100. message.hide()
  101. eject_message = Message(catalog.i18nc("@info:status", "Ejected {0}. You can now safely remove the drive.").format(self.getName()))
  102. else:
  103. eject_message = Message(catalog.i18nc("@info:status", "Failed to eject {0}. Another program may be using the drive.").format(self.getName()))
  104. eject_message.show()