TimeLapse.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. # Modified 5/15/2023 - Greg Valiant (Greg Foresi)
  2. # Created by Wayne Porter
  3. # Added insertion frequency
  4. # Adjusted for use with Relative Extrusion
  5. # Changed Retract to a boolean and when true use the regular Cura retract settings.
  6. # Use the regular Cura settings for Travel Speed and Speed_Z instead of asking.
  7. # Added code to check the E location to prevent retracts if the filament was already retracted.
  8. # Added 'Pause before image' per LemanRus
  9. from ..Script import Script
  10. from UM.Application import Application
  11. from UM.Logger import Logger
  12. class TimeLapse(Script):
  13. def __init__(self):
  14. super().__init__()
  15. def getSettingDataString(self):
  16. return """{
  17. "name": "Time Lapse Camera",
  18. "key": "TimeLapse",
  19. "metadata": {},
  20. "version": 2,
  21. "settings":
  22. {
  23. "trigger_command":
  24. {
  25. "label": "Camera Trigger Command",
  26. "description": "G-code command used to trigger the camera. The setting box will take any command and parameters.",
  27. "type": "str",
  28. "default_value": "M240"
  29. },
  30. "insert_frequency":
  31. {
  32. "label": "How often (layers)",
  33. "description": "Every so many layers (always starts at the first layer whether it's the model or a raft).",
  34. "type": "enum",
  35. "options": {
  36. "every_layer": "Every Layer",
  37. "every_2nd": "Every 2nd",
  38. "every_3rd": "Every 3rd",
  39. "every_5th": "Every 5th",
  40. "every_10th": "Every 10th",
  41. "every_25th": "Every 25th",
  42. "every_50th": "Every 50th",
  43. "every_100th": "Every 100th"},
  44. "default_value": "every_layer"
  45. },
  46. "anti_shake_length":
  47. {
  48. "label": "Pause before image",
  49. "description": "How long to wait (in ms) before capturing the image. This is to allow the printer to 'settle down' after movement. To disable set this to '0'.",
  50. "type": "int",
  51. "default_value": 0,
  52. "minimum_value": 0,
  53. "unit": "ms"
  54. },
  55. "pause_length":
  56. {
  57. "label": "Pause after image",
  58. "description": "How long to wait (in ms) after camera was triggered.",
  59. "type": "int",
  60. "default_value": 500,
  61. "minimum_value": 0,
  62. "unit": "ms"
  63. },
  64. "park_print_head":
  65. {
  66. "label": "Park Print Head",
  67. "description": "Park the print head out of the way.",
  68. "type": "bool",
  69. "default_value": true
  70. },
  71. "head_park_x":
  72. {
  73. "label": "Park Print Head X",
  74. "description": "What X location does the head move to for photo.",
  75. "unit": "mm",
  76. "type": "float",
  77. "default_value": 0,
  78. "enabled": "park_print_head"
  79. },
  80. "head_park_y":
  81. {
  82. "label": "Park Print Head Y",
  83. "description": "What Y location does the head move to for photo.",
  84. "unit": "mm",
  85. "type": "float",
  86. "default_value": 0,
  87. "enabled": "park_print_head"
  88. },
  89. "retract":
  90. {
  91. "label": "Retract when required",
  92. "description": "Retract if there isn't already a retraction. If unchecked then there will be no retraction even if there is none in the gcode. If retractions are not enabled in Cura there won't be a retraction. regardless of this setting.",
  93. "type": "bool",
  94. "default_value": true
  95. },
  96. "zhop":
  97. {
  98. "label": "Z-Hop Height When Parking",
  99. "description": "The height to lift the nozzle off the print before parking.",
  100. "unit": "mm",
  101. "type": "float",
  102. "default_value": 2.0,
  103. "minimum_value": 0.0
  104. },
  105. "ensure_final_image":
  106. {
  107. "label": "Ensure Final Image",
  108. "description": "Depending on how the layer numbers work out with the 'How Often' frequency there might not be an image taken at the end of the last layer. This will ensure that one is taken. There is no parking as the Ending Gcode comes right up.",
  109. "type": "bool",
  110. "default_value": false
  111. }
  112. }
  113. }"""
  114. def execute(self, data):
  115. mycura = Application.getInstance().getGlobalContainerStack()
  116. relative_extrusion = bool(mycura.getProperty("relative_extrusion", "value"))
  117. extruder = mycura.extruderList
  118. retract_speed = int(extruder[0].getProperty("retraction_speed", "value"))*60
  119. retract_dist = round(float(extruder[0].getProperty("retraction_amount", "value")), 2)
  120. retract_enabled = bool(extruder[0].getProperty("retraction_enable", "value"))
  121. firmware_retract = bool(mycura.getProperty("machine_firmware_retract", "value"))
  122. speed_z = int(extruder[0].getProperty("speed_z_hop", "value"))*60
  123. if relative_extrusion:
  124. rel_cmd = 83
  125. else:
  126. rel_cmd = 82
  127. travel_speed = int(extruder[0].getProperty("speed_travel", "value"))*60
  128. park_print_head = self.getSettingValueByKey("park_print_head")
  129. x_park = self.getSettingValueByKey("head_park_x")
  130. y_park = self.getSettingValueByKey("head_park_y")
  131. trigger_command = self.getSettingValueByKey("trigger_command")
  132. pause_length = self.getSettingValueByKey("pause_length")
  133. retract = bool(self.getSettingValueByKey("retract"))
  134. zhop = self.getSettingValueByKey("zhop")
  135. ensure_final_image = bool(self.getSettingValueByKey("ensure_final_image"))
  136. when_to_insert = self.getSettingValueByKey("insert_frequency")
  137. last_x = 0
  138. last_y = 0
  139. last_z = 0
  140. last_e = 0
  141. prev_e = 0
  142. is_retracted = False
  143. gcode_to_append = ""
  144. if park_print_head:
  145. gcode_to_append += f"G0 F{travel_speed} X{x_park} Y{y_park} ;Park print head\n"
  146. gcode_to_append += "M400 ;Wait for moves to finish\n"
  147. anti_shake_length = self.getSettingValueByKey("anti_shake_length")
  148. if anti_shake_length > 0:
  149. gcode_to_append += f"G4 P{anti_shake_length} ;Wait for printer to settle down\n"
  150. gcode_to_append += trigger_command + " ;Snap the Image\n"
  151. gcode_to_append += f"G4 P{pause_length} ;Wait for camera to finish\n"
  152. match when_to_insert:
  153. case "every_layer":
  154. step_freq = 1
  155. case "every_2nd":
  156. step_freq = 2
  157. case "every_3rd":
  158. step_freq = 3
  159. case "every_5th":
  160. step_freq = 5
  161. case "every_10th":
  162. step_freq = 10
  163. case "every_25th":
  164. step_freq = 25
  165. case "every_50th":
  166. step_freq = 50
  167. case "every_100th":
  168. step_freq = 100
  169. case _:
  170. step_freq = 1
  171. # Use the step_freq to index through the layers----------------------------------------
  172. for num in range(2,len(data)-1,step_freq):
  173. layer = data[num]
  174. try:
  175. # Track X,Y,Z location.--------------------------------------------------------
  176. for line in layer.split("\n"):
  177. if self.getValue(line, "G") in {0, 1}:
  178. last_x = self.getValue(line, "X", last_x)
  179. last_y = self.getValue(line, "Y", last_y)
  180. last_z = self.getValue(line, "Z", last_z)
  181. #Track the E location so that if there is already a retraction we don't double dip.
  182. if rel_cmd == 82:
  183. if " E" in line:
  184. last_e = line.split("E")[1]
  185. if float(last_e) < float(prev_e):
  186. is_retracted = True
  187. else:
  188. is_retracted = False
  189. prev_e = last_e
  190. elif rel_cmd == 83:
  191. if " E" in line:
  192. last_e = line.split("E")[1]
  193. if float(last_e) < 0:
  194. is_retracted = True
  195. else:
  196. is_retracted = False
  197. prev_e = last_e
  198. if firmware_retract and self.getValue(line, "G") in {10, 11}:
  199. if self.getValue(line, "G") == 10:
  200. is_retracted = True
  201. last_e = float(prev_e) - float(retract_dist)
  202. if self.getValue(line, "G") == 11:
  203. is_retracted = False
  204. last_e = float(prev_e) + float(retract_dist)
  205. prev_e = last_e
  206. lines = layer.split("\n")
  207. # Insert the code----------------------------------------------------
  208. camera_code = ""
  209. for line in lines:
  210. if ";LAYER:" in line:
  211. if retract and not is_retracted and retract_enabled: # Retract unless already retracted
  212. camera_code += ";TYPE:CUSTOM-----------------TimeLapse Begin\n"
  213. camera_code += "M83 ;Extrude Relative\n"
  214. if not firmware_retract:
  215. camera_code += f"G1 F{retract_speed} E-{retract_dist} ;Retract filament\n"
  216. else:
  217. camera_code += "G10 ;Retract filament\n"
  218. else:
  219. camera_code += ";TYPE:CUSTOM-----------------TimeLapse Begin\n"
  220. if zhop != 0:
  221. camera_code += f"G1 F{speed_z} Z{round(last_z + zhop,2)} ;Z-Hop\n"
  222. camera_code += gcode_to_append
  223. camera_code += f"G0 F{travel_speed} X{last_x} Y{last_y} ;Restore XY position\n"
  224. if zhop != 0:
  225. camera_code += f"G0 F{speed_z} Z{last_z} ;Restore Z position\n"
  226. if retract and not is_retracted and retract_enabled:
  227. if not firmware_retract:
  228. camera_code += f"G1 F{retract_speed} E{retract_dist} ;Un-Retract filament\n"
  229. else:
  230. camera_code += "G11 ;Un-Retract filament\n"
  231. camera_code += f"M{rel_cmd} ;Extrude Mode\n"
  232. camera_code += f";{'-' * 28}TimeLapse End"
  233. # Format the camera code to be inserted
  234. temp_lines = camera_code.split("\n")
  235. for temp_index, temp_line in enumerate(temp_lines):
  236. if ";" in temp_line and not temp_line.startswith(";"):
  237. temp_lines[temp_index] = temp_line.replace(temp_line.split(";")[0], temp_line.split(";")[0] + str(" " * (29 - len(temp_line.split(";")[0]))),1)
  238. temp_lines = "\n".join(temp_lines)
  239. lines.insert(len(lines) - 2, temp_lines)
  240. data[num] = "\n".join(lines)
  241. break
  242. except Exception as e:
  243. Logger.log("w", "TimeLapse Error: " + repr(e))
  244. # Take a final image if there was no camera shot at the end of the last layer.
  245. if "TimeLapse Begin" not in data[len(data) - (3 if retract_enabled else 2)] and ensure_final_image:
  246. data[len(data)-1] = "M400 ; Wait for all moves to finish\n" + trigger_command + " ;Snap the final Image\n" + f"G4 P{pause_length} ;Wait for camera\n" + data[len(data)-1]
  247. return data