DisplayProgressOnLCD.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # Cura PostProcessingPlugin
  2. # Author: Mathias Lyngklip Kjeldgaard, Alexander Gee, Kimmo Toivanen, Inigo Martinez
  3. # Date: July 31, 2019
  4. # Modified: Nov 30, 2021
  5. # Description: This plugin displays progress on the LCD. It can output the estimated time remaining and the completion percentage.
  6. from ..Script import Script
  7. import re
  8. import datetime
  9. class DisplayProgressOnLCD(Script):
  10. def __init__(self):
  11. super().__init__()
  12. def getSettingDataString(self):
  13. return """{
  14. "name": "Display Progress On LCD",
  15. "key": "DisplayProgressOnLCD",
  16. "metadata": {},
  17. "version": 2,
  18. "settings":
  19. {
  20. "time_remaining":
  21. {
  22. "label": "Time Remaining",
  23. "description": "Select to write remaining time to the display.Select to write remaining time on the display using M117 status line message (almost all printers) or using M73 command (Prusa and Marlin 2 if enabled).",
  24. "type": "bool",
  25. "default_value": false
  26. },
  27. "time_remaining_method":
  28. {
  29. "label": "Time Reporting Method",
  30. "description": "How should remaining time be shown on the display? It could use a generic message command (M117, almost all printers), or a specialised time remaining command (M73, Prusa and Marlin 2).",
  31. "type": "enum",
  32. "options": {
  33. "m117":"M117 - All printers",
  34. "m73":"M73 - Prusa, Marlin 2",
  35. "m118":"M118 - Octoprint"
  36. },
  37. "enabled": "time_remaining",
  38. "default_value": "m117"
  39. },
  40. "update_frequency":
  41. {
  42. "label": "Update frequency",
  43. "description": "Update remaining time for every layer or periodically every minute or faster.",
  44. "type": "enum",
  45. "options": {"0":"Every layer","15":"Every 15 seconds","30":"Every 30 seconds","60":"Every minute"},
  46. "default_value": "0",
  47. "enabled": "time_remaining"
  48. },
  49. "percentage":
  50. {
  51. "label": "Percentage",
  52. "description": "When enabled, set the completion bar percentage on the LCD using Marlin's M73 command.",
  53. "type": "bool",
  54. "default_value": false
  55. }
  56. }
  57. }"""
  58. # Get the time value from a line as a float.
  59. # Example line ;TIME_ELAPSED:1234.6789 or ;TIME:1337
  60. def getTimeValue(self, line):
  61. list_split = re.split(":", line) # Split at ":" so we can get the numerical value
  62. return float(list_split[1]) # Convert the numerical portion to a float
  63. def outputTime(self, lines, line_index, time_left, mode):
  64. # Do some math to get the time left in seconds into the right format. (HH,MM,SS)
  65. time_left = max(time_left, 0)
  66. m, s = divmod(time_left, 60)
  67. h, m = divmod(m, 60)
  68. # Create the string
  69. if mode == "m117":
  70. current_time_string = "{:d}h{:02d}m{:02d}s".format(int(h), int(m), int(s))
  71. # And now insert that into the GCODE
  72. lines.insert(line_index, "M117 Time Left {}".format(current_time_string))
  73. elif mode == "m118":
  74. current_time_string = "{:d}h{:02d}m{:02d}s".format(int(h), int(m), int(s))
  75. # And now insert that into the GCODE
  76. lines.insert(line_index, "M118 A1 P0 action:notification Time Left {}".format(current_time_string))
  77. else:
  78. mins = int(60 * h + m + s / 30)
  79. lines.insert(line_index, "M73 R{}".format(mins))
  80. def execute(self, data):
  81. output_time = self.getSettingValueByKey("time_remaining")
  82. output_time_method = self.getSettingValueByKey("time_remaining_method")
  83. output_frequency = int(self.getSettingValueByKey("update_frequency"))
  84. output_percentage = self.getSettingValueByKey("percentage")
  85. line_set = {}
  86. if output_percentage or output_time:
  87. total_time = -1
  88. previous_layer_end_percentage = 0
  89. previous_layer_end_time = 0
  90. for layer in data:
  91. layer_index = data.index(layer)
  92. lines = layer.split("\n")
  93. for line in lines:
  94. if (line.startswith(";TIME:") or line.startswith(";PRINT.TIME:")) and total_time == -1:
  95. # This line represents the total time required to print the gcode
  96. total_time = self.getTimeValue(line)
  97. line_index = lines.index(line)
  98. # In the beginning we may have 2 M73 lines, but it makes logic less complicated
  99. if output_time:
  100. self.outputTime(lines, line_index, total_time, output_time_method)
  101. if output_percentage:
  102. # Emit 0 percent to sure Marlin knows we are overriding the completion percentage
  103. if output_time_method == "m118":
  104. lines.insert(line_index, "M118 A1 P0 action:notification Data Left 0/100")
  105. else:
  106. lines.insert(line_index, "M73 P0")
  107. elif line.startswith(";TIME_ELAPSED:"):
  108. # We've found one of the time elapsed values which are added at the end of layers
  109. # If we have seen this line before then skip processing it. We can see lines multiple times because we are adding
  110. # intermediate percentages before the line being processed. This can cause the current line to shift back and be
  111. # encountered more than once
  112. if line in line_set:
  113. continue
  114. line_set[line] = True
  115. # If total_time was not already found then noop
  116. if total_time == -1:
  117. continue
  118. current_time = self.getTimeValue(line)
  119. line_index = lines.index(line)
  120. if output_time:
  121. if output_frequency == 0:
  122. # Here we calculate remaining time
  123. self.outputTime(lines, line_index, total_time - current_time, output_time_method)
  124. else:
  125. # Here we calculate remaining time and how many outputs are expected for the layer
  126. layer_time_delta = int(current_time - previous_layer_end_time)
  127. layer_step_delta = int((current_time - previous_layer_end_time) / output_frequency)
  128. # If this layer represents less than 1 step then we don't need to emit anything, continue to the next layer
  129. if layer_step_delta != 0:
  130. # Grab the index of the current line and figure out how many lines represent one second
  131. step = line_index / layer_time_delta
  132. # Move new lines further as we add new lines above it
  133. lines_added = 1
  134. # Run through layer in seconds
  135. for seconds in range(1, layer_time_delta + 1):
  136. # Time from start to decide when to update
  137. line_time = int(previous_layer_end_time + seconds)
  138. # Output every X seconds and after last layer
  139. if line_time % output_frequency == 0 or line_time == total_time:
  140. # Line to add the output
  141. time_line_index = int((seconds * step) + lines_added)
  142. # Insert remaining time into the GCODE
  143. self.outputTime(lines, time_line_index, total_time - line_time, output_time_method)
  144. # Next line will be again lower
  145. lines_added = lines_added + 1
  146. previous_layer_end_time = int(current_time)
  147. if output_percentage:
  148. # Calculate percentage value this layer ends at
  149. layer_end_percentage = int((current_time / total_time) * 100)
  150. # Figure out how many percent of the total time is spent in this layer
  151. layer_percentage_delta = layer_end_percentage - previous_layer_end_percentage
  152. # If this layer represents less than 1 percent then we don't need to emit anything, continue to the next layer
  153. if layer_percentage_delta != 0:
  154. # Grab the index of the current line and figure out how many lines represent one percent
  155. step = line_index / layer_percentage_delta
  156. for percentage in range(1, layer_percentage_delta + 1):
  157. # We add the percentage value here as while processing prior lines we will have inserted
  158. # percentage lines before the current one. Failing to do this will upset the spacing
  159. percentage_line_index = int((percentage * step) + percentage)
  160. # Due to integer truncation of the total time value in the gcode the percentage we
  161. # calculate may slightly exceed 100, as that is not valid we cap the value here
  162. output = min(percentage + previous_layer_end_percentage, 100)
  163. # Now insert the sanitized percentage into the GCODE
  164. if output_time_method == "m118":
  165. lines.insert(percentage_line_index, "M118 A1 P0 action:notification Data Left {}/100".format(output))
  166. else:
  167. lines.insert(percentage_line_index, "M73 P{}".format(output))
  168. previous_layer_end_percentage = layer_end_percentage
  169. # Join up the lines for this layer again and store them in the data array
  170. data[layer_index] = "\n".join(lines)
  171. return data