GCodeProfileReader.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. # Copyright (c) 2018 Ultimaker B.V.
  2. # Cura is released under the terms of the LGPLv3 or higher.
  3. import re #Regular expressions for parsing escape characters in the settings.
  4. import json
  5. from UM.Settings.ContainerFormatError import ContainerFormatError
  6. from UM.Settings.InstanceContainer import InstanceContainer
  7. from UM.Logger import Logger
  8. from UM.i18n import i18nCatalog
  9. catalog = i18nCatalog("cura")
  10. from cura.ProfileReader import ProfileReader, NoProfileException
  11. ## A class that reads profile data from g-code files.
  12. #
  13. # It reads the profile data from g-code files and stores it in a new profile.
  14. # This class currently does not process the rest of the g-code in any way.
  15. class GCodeProfileReader(ProfileReader):
  16. ## The file format version of the serialized g-code.
  17. #
  18. # It can only read settings with the same version as the version it was
  19. # written with. If the file format is changed in a way that breaks reverse
  20. # compatibility, increment this version number!
  21. version = 3
  22. ## Dictionary that defines how characters are escaped when embedded in
  23. # g-code.
  24. #
  25. # Note that the keys of this dictionary are regex strings. The values are
  26. # not.
  27. escape_characters = {
  28. re.escape("\\\\"): "\\", #The escape character.
  29. re.escape("\\n"): "\n", #Newlines. They break off the comment.
  30. re.escape("\\r"): "\r" #Carriage return. Windows users may need this for visualisation in their editors.
  31. }
  32. ## Initialises the g-code reader as a profile reader.
  33. def __init__(self):
  34. super().__init__()
  35. ## Reads a g-code file, loading the profile from it.
  36. #
  37. # \param file_name The name of the file to read the profile from.
  38. # \return The profile that was in the specified file, if any. If the
  39. # specified file was no g-code or contained no parsable profile, \code
  40. # None \endcode is returned.
  41. def read(self, file_name):
  42. if file_name.split(".")[-1] != "gcode":
  43. return None
  44. prefix = ";SETTING_" + str(GCodeProfileReader.version) + " "
  45. prefix_length = len(prefix)
  46. # Loading all settings from the file.
  47. # They are all at the end, but Python has no reverse seek any more since Python3.
  48. # TODO: Consider moving settings to the start?
  49. serialized = "" # Will be filled with the serialized profile.
  50. try:
  51. with open(file_name, "r") as f:
  52. for line in f:
  53. if line.startswith(prefix):
  54. # Remove the prefix and the newline from the line and add it to the rest.
  55. serialized += line[prefix_length : -1]
  56. except IOError as e:
  57. Logger.log("e", "Unable to open file %s for reading: %s", file_name, str(e))
  58. return None
  59. serialized = unescapeGcodeComment(serialized)
  60. serialized = serialized.strip()
  61. if not serialized:
  62. Logger.log("i", "No custom profile to import from this g-code: %s", file_name)
  63. raise NoProfileException()
  64. # serialized data can be invalid JSON
  65. try:
  66. json_data = json.loads(serialized)
  67. except Exception as e:
  68. Logger.log("e", "Could not parse serialized JSON data from g-code %s, error: %s", file_name, e)
  69. return None
  70. profiles = []
  71. global_profile = readQualityProfileFromString(json_data["global_quality"])
  72. # This is a fix for profiles created with 2.3.0 For some reason it added the "extruder" property to the
  73. # global profile.
  74. # The fix is simple and safe, as a global profile should never have the extruder entry.
  75. if global_profile.getMetaDataEntry("extruder", None) is not None:
  76. global_profile.setMetaDataEntry("extruder", None)
  77. profiles.append(global_profile)
  78. for profile_string in json_data.get("extruder_quality", []):
  79. profiles.append(readQualityProfileFromString(profile_string))
  80. return profiles
  81. ## Unescape a string which has been escaped for use in a gcode comment.
  82. #
  83. # \param string The string to unescape.
  84. # \return \type{str} The unscaped string.
  85. def unescapeGcodeComment(string):
  86. # Un-escape the serialized profile.
  87. pattern = re.compile("|".join(GCodeProfileReader.escape_characters.keys()))
  88. # Perform the replacement with a regular expression.
  89. return pattern.sub(lambda m: GCodeProfileReader.escape_characters[re.escape(m.group(0))], string)
  90. ## Read in a profile from a serialized string.
  91. #
  92. # \param profile_string The profile data in serialized form.
  93. # \return \type{Profile} the resulting Profile object or None if it could not be read.
  94. def readQualityProfileFromString(profile_string):
  95. # Create an empty profile - the id and name will be changed by the ContainerRegistry
  96. profile = InstanceContainer("")
  97. try:
  98. profile.deserialize(profile_string)
  99. except ContainerFormatError as e:
  100. Logger.log("e", "Corrupt profile in this g-code file: %s", str(e))
  101. return None
  102. except Exception as e: # Not a valid g-code file.
  103. Logger.log("e", "Unable to serialise the profile: %s", str(e))
  104. return None
  105. return profile