MeshFormatHandler.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. # Copyright (c) 2019 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import io
  4. from typing import Optional, Dict, Union, List, cast
  5. from UM.FileHandler.FileHandler import FileHandler
  6. from UM.FileHandler.FileWriter import FileWriter
  7. from UM.Logger import Logger
  8. from UM.OutputDevice import OutputDeviceError # To show that something went wrong when writing.
  9. from UM.Scene.SceneNode import SceneNode
  10. from UM.Version import Version # To check against firmware versions for support.
  11. from UM.i18n import i18nCatalog
  12. from cura.CuraApplication import CuraApplication
  13. I18N_CATALOG = i18nCatalog("cura")
  14. class MeshFormatHandler:
  15. """This class is responsible for choosing the formats used by the connected clusters."""
  16. def __init__(self, file_handler: Optional[FileHandler], firmware_version: str) -> None:
  17. self._file_handler = file_handler or CuraApplication.getInstance().getMeshFileHandler()
  18. self._preferred_format = self._getPreferredFormat(firmware_version)
  19. self._writer = self._getWriter(self.mime_type) if self._preferred_format else None
  20. @property
  21. def is_valid(self) -> bool:
  22. return bool(self._writer)
  23. @property
  24. def preferred_format(self) -> Dict[str, Union[str, int, bool]]:
  25. """Chooses the preferred file format.
  26. :return: A dict with the file format details, with the following keys:
  27. {id: str, extension: str, description: str, mime_type: str, mode: int, hide_in_file_dialog: bool}
  28. """
  29. return self._preferred_format
  30. @property
  31. def writer(self) -> Optional[FileWriter]:
  32. """Gets the file writer for the given file handler and mime type.
  33. :return: A file writer.
  34. """
  35. return self._writer
  36. @property
  37. def mime_type(self) -> str:
  38. return cast(str, self._preferred_format["mime_type"])
  39. @property
  40. def file_mode(self) -> int:
  41. """Gets the file mode (FileWriter.OutputMode.TextMode or FileWriter.OutputMode.BinaryMode)"""
  42. return cast(int, self._preferred_format["mode"])
  43. @property
  44. def file_extension(self) -> str:
  45. """Gets the file extension"""
  46. return cast(str, self._preferred_format["extension"])
  47. def createStream(self) -> Union[io.BytesIO, io.StringIO]:
  48. """Creates the right kind of stream based on the preferred format."""
  49. if self.file_mode == FileWriter.OutputMode.TextMode:
  50. return io.StringIO()
  51. else:
  52. return io.BytesIO()
  53. def getBytes(self, nodes: List[SceneNode]) -> bytes:
  54. """Writes the mesh and returns its value."""
  55. if self.writer is None:
  56. raise ValueError("There is no writer for the mesh format handler.")
  57. stream = self.createStream()
  58. self.writer.write(stream, nodes)
  59. value = stream.getvalue()
  60. if isinstance(value, str):
  61. value = value.encode()
  62. return value
  63. def _getPreferredFormat(self, firmware_version: str) -> Dict[str, Union[str, int, bool]]:
  64. """Chooses the preferred file format for the given file handler.
  65. :param firmware_version: The version of the firmware.
  66. :return: A dict with the file format details.
  67. """
  68. # Formats supported by this application (file types that we can actually write).
  69. application = CuraApplication.getInstance()
  70. file_formats = self._file_handler.getSupportedFileTypesWrite()
  71. global_stack = application.getGlobalContainerStack()
  72. # Create a list from the supported file formats string.
  73. if not global_stack:
  74. Logger.log("e", "Missing global stack!")
  75. return {}
  76. machine_file_formats = global_stack.getMetaDataEntry("file_formats").split(";")
  77. machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
  78. # Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
  79. if "application/x-ufp" not in machine_file_formats and Version(firmware_version) >= Version("4.4"):
  80. machine_file_formats = ["application/x-ufp"] + machine_file_formats
  81. # Take the intersection between file_formats and machine_file_formats.
  82. format_by_mimetype = {f["mime_type"]: f for f in file_formats}
  83. # Keep them ordered according to the preference in machine_file_formats.
  84. file_formats = [format_by_mimetype[mimetype] for mimetype in machine_file_formats]
  85. if len(file_formats) == 0:
  86. Logger.log("e", "There are no file formats available to write with!")
  87. raise OutputDeviceError.WriteRequestFailedError(
  88. I18N_CATALOG.i18nc("@info:status", "There are no file formats available to write with!")
  89. )
  90. return file_formats[0]
  91. def _getWriter(self, mime_type: str) -> Optional[FileWriter]:
  92. """Gets the file writer for the given file handler and mime type.
  93. :param mime_type: The mine type.
  94. :return: A file writer.
  95. """
  96. # Just take the first file format available.
  97. return self._file_handler.getWriterByMimeType(mime_type)