AddCoolingProfile.py 45 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. # Designed in January 2023 by GregValiant (Greg Foresi)
  2. ## My design intent was to make this as full featured and "industrial strength" as I could. People printing exotic materials on large custom printers may want to turn the fans off for certain layers, and then back on again later in the print. This script allows that.
  3. # Functions:
  4. ## Remove all fan speed lines from the file (optional). This should be enabled for the first instance of the script. It is disabled by default in any following instances.
  5. ## "By Layer" allows the user to adjust the fan speed up, or down, or off, within the print. "By Feature" allows different fan speeds for different features (;TYPE:WALL-OUTER, etc.).
  6. ## If 'By Feature' then a Start Layer and/or an End Layer can be defined.
  7. ## Fan speeds are scaled PWM (0 - 255) or RepRap (0.0 - 1.0) depending on {machine_scale_fan_speed_zero_to_one}.
  8. ## A minimum fan speed of 12% is enforced. It is the slowest speed that my cooling fan will turn on so that's what I used. 'M106 S14' (as Cura might insert) was pretty useless.
  9. ## If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or feature setting. There is support for up to 4 layer cooling fan circuits.
  10. ## My thanks to @5axes(@CUQ), @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir. A special thanks to @RBurema for his patience in reviewing my 'non-pythonic' script.
  11. ## 9/14/23 (Greg Foresi) Added support for One-at-a-Time print sequence.
  12. ## 12/15/23 (Greg Foresi) Split off 'Single Fan By Layer', 'Multi-fan By Layer', 'Single Fan By Feature', and 'Multi-fan By Feature' from the main 'execute' script.
  13. ## 1/5/24 (Greg Foresi) Revised the regex replacements.
  14. from ..Script import Script
  15. from UM.Application import Application
  16. import re
  17. class AddCoolingProfile(Script):
  18. def getSettingDataString(self):
  19. return """{
  20. "name": "Advanced Cooling Fan Control",
  21. "key": "AddCoolingProfile",
  22. "metadata": {},
  23. "version": 2,
  24. "settings":
  25. {
  26. "fan_layer_or_feature":
  27. {
  28. "label": "Cooling Control by:",
  29. "description": "A fan percentage of ''0'' turns the fan off. Minimum Fan is 12% (when on). All layer entries are the Cura Preview number. ''By Layer'': Enter as ''Layer#/Fan%'' (foreslash is the delimiter). Your final layer speed will continue to the end of the Gcode. ''By Feature'': If you enable an 'End Layer' then the ''Final %'' is available and is the speed that will finish the file. 'By Feature' is better for large slow prints than it is for short fast prints.",
  30. "type": "enum",
  31. "options": {
  32. "by_layer": "Layer Numbers",
  33. "by_feature": "Feature Types"},
  34. "default_value": "by_layer"
  35. },
  36. "delete_existing_m106":
  37. {
  38. "label": "Remove M106 lines prior to inserting new.",
  39. "description": "If you have 2 or more instances of 'Advanced Cooling Fan Control' running (to cool a portion of a print differently), then you must uncheck this box or the followup instances will remove all the lines inserted by the first instance. Pay attention to the Start and Stop layers. Regardless of this setting: The script always removes M106 lines starting with the lowest layer number (when 'By Layer') or the starting layer number (when 'By Feature'). If you want to keep the M106 lines that Cura inserted up to the point where this post-processor will start making insertions, then un-check the box.",
  40. "type": "bool",
  41. "enabled": true,
  42. "value": true,
  43. "default_value": true
  44. },
  45. "feature_fan_start_layer":
  46. {
  47. "label": "Starting Layer",
  48. "description": "Layer to start the insertion at. Use the Cura preview numbers. Changes will begin at the start of that layer.",
  49. "type": "int",
  50. "default_value": 5,
  51. "minimum_value": 1,
  52. "unit": "Lay# ",
  53. "enabled": "fan_layer_or_feature == 'by_feature'"
  54. },
  55. "feature_fan_end_layer":
  56. {
  57. "label": "Ending Layer",
  58. "description": "Layer to complete the insertion at. Enter '-1' for the entire file or enter a layer number. Insertions will stop at the END of this layer. If you set an End Layer then you should set the Final % that will finish the file",
  59. "type": "int",
  60. "default_value": -1,
  61. "minimum_value": -1,
  62. "unit": "Lay# ",
  63. "enabled": "fan_layer_or_feature == 'by_feature'"
  64. },
  65. "layer_fan_1":
  66. {
  67. "label": "Layer/Percent #1",
  68. "description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage. There are up to 8 changes. If you need more then add a second instance of this script and remember to turn off 'Remove M106 lines' in the second instance. The layer numbers in the second instance must start with a layer number higher than the last layer number in a previous script. You can't end the first script with a setting for layer 80 and then start the second script with a setting for layer 40 because 'Remove M106 lines' always starts with the lowest layer number when 'By Layer' is selected.",
  69. "type": "str",
  70. "default_value": "5/30",
  71. "unit": "L#/% ",
  72. "enabled": "fan_layer_or_feature == 'by_layer'"
  73. },
  74. "layer_fan_2":
  75. {
  76. "label": "Layer/Percent #2",
  77. "description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
  78. "type": "str",
  79. "default_value": "",
  80. "unit": "L#/% ",
  81. "enabled": "fan_layer_or_feature == 'by_layer'"
  82. },
  83. "layer_fan_3":
  84. {
  85. "label": "Layer/Percent #3",
  86. "description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
  87. "type": "str",
  88. "default_value": "",
  89. "unit": "L#/% ",
  90. "enabled": "fan_layer_or_feature == 'by_layer'"
  91. },
  92. "layer_fan_4":
  93. {
  94. "label": "Layer/Percent #4",
  95. "description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
  96. "type": "str",
  97. "default_value": "",
  98. "unit": "L#/% ",
  99. "enabled": "fan_layer_or_feature == 'by_layer'"
  100. },
  101. "layer_fan_5":
  102. {
  103. "label": "Layer/Percent #5",
  104. "description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
  105. "type": "str",
  106. "default_value": "",
  107. "unit": "L#/% ",
  108. "enabled": "fan_layer_or_feature == 'by_layer'"
  109. },
  110. "layer_fan_6":
  111. {
  112. "label": "Layer/Percent #6",
  113. "description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
  114. "type": "str",
  115. "default_value": "",
  116. "unit": "L#/% ",
  117. "enabled": "fan_layer_or_feature == 'by_layer'"
  118. },
  119. "layer_fan_7":
  120. {
  121. "label": "Layer/Percent #7",
  122. "description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
  123. "type": "str",
  124. "default_value": "",
  125. "unit": "L#/% ",
  126. "enabled": "fan_layer_or_feature == 'by_layer'"
  127. },
  128. "layer_fan_8":
  129. {
  130. "label": "Layer/Percent #8",
  131. "description": "Enter as: 'LAYER / Percent' Ex: 55/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
  132. "type": "str",
  133. "default_value": "",
  134. "unit": "L#/% ",
  135. "enabled": "fan_layer_or_feature == 'by_layer'"
  136. },
  137. "feature_fan_skirt":
  138. {
  139. "label": "Skirt/Brim/Ooze Shield %",
  140. "description": "Enter the fan percentage for skirt/brim. If you are starting at a layer above 1 then this setting only affects Ooze Shields and from the Start Layer up.",
  141. "type": "int",
  142. "default_value": 0,
  143. "minimum_value": 0,
  144. "maximum_value": 100,
  145. "unit": "% ",
  146. "enabled": "fan_layer_or_feature == 'by_feature'"
  147. },
  148. "feature_fan_wall_inner":
  149. {
  150. "label": "Inner Walls %",
  151. "description": "Enter the fan percentage for the Wall-Inner.",
  152. "type": "int",
  153. "default_value": 35,
  154. "minimum_value": 0,
  155. "maximum_value": 100,
  156. "unit": "% ",
  157. "enabled": "fan_layer_or_feature == 'by_feature'"
  158. },
  159. "feature_fan_wall_outer":
  160. {
  161. "label": "Outer Walls %",
  162. "description": "Enter the fan percentage for the Wall-Outer.",
  163. "type": "int",
  164. "default_value": 75,
  165. "minimum_value": 0,
  166. "maximum_value": 100,
  167. "unit": "% ",
  168. "enabled": "fan_layer_or_feature == 'by_feature'"
  169. },
  170. "feature_fan_fill":
  171. {
  172. "label": "Infill %",
  173. "description": "Enter the fan percentage for the Infill.",
  174. "type": "int",
  175. "default_value": 35,
  176. "minimum_value": 0,
  177. "maximum_value": 100,
  178. "unit": "% ",
  179. "enabled": "fan_layer_or_feature == 'by_feature'"
  180. },
  181. "feature_fan_skin":
  182. {
  183. "label": "Top/Bottom (Skin) %",
  184. "description": "Enter the fan percentage for the Skins.",
  185. "type": "int",
  186. "default_value": 100,
  187. "minimum_value": 0,
  188. "maximum_value": 100,
  189. "unit": "% ",
  190. "enabled": "fan_layer_or_feature == 'by_feature'"
  191. },
  192. "feature_fan_support":
  193. {
  194. "label": "Support %",
  195. "description": "Enter the fan percentage for the Supports.",
  196. "type": "int",
  197. "default_value": 35,
  198. "minimum_value": 0,
  199. "maximum_value": 100,
  200. "unit": "% ",
  201. "enabled": "fan_layer_or_feature == 'by_feature'"
  202. },
  203. "feature_fan_support_interface":
  204. {
  205. "label": "Support Interface %",
  206. "description": "Enter the fan percentage for the Support Interface.",
  207. "type": "int",
  208. "default_value": 100,
  209. "minimum_value": 0,
  210. "maximum_value": 100,
  211. "unit": "% ",
  212. "enabled": "fan_layer_or_feature == 'by_feature'"
  213. },
  214. "feature_fan_prime_tower":
  215. {
  216. "label": "Prime Tower %",
  217. "description": "Enter the fan percentage for the Prime Tower (whether it's used or not).",
  218. "type": "int",
  219. "default_value": 35,
  220. "minimum_value": 0,
  221. "maximum_value": 100,
  222. "unit": "% ",
  223. "enabled": "fan_layer_or_feature == 'by_feature'"
  224. },
  225. "feature_fan_bridge":
  226. {
  227. "label": "Bridge %",
  228. "description": "Enter the fan percentage for any Bridging (whether it's used on not).",
  229. "type": "int",
  230. "default_value": 100,
  231. "minimum_value": 0,
  232. "maximum_value": 100,
  233. "unit": "% ",
  234. "enabled": "fan_layer_or_feature == 'by_feature'"
  235. },
  236. "feature_fan_combing":
  237. {
  238. "label": "Fan 'OFF' during Combing:",
  239. "description": "When checked will set the fan to 0% for combing moves over 5 lines long in the gcode. When un-checked the fan speed during combing is whatever the previous speed is set to.",
  240. "type": "bool",
  241. "enabled": "fan_layer_or_feature == 'by_feature'",
  242. "default_value": true
  243. },
  244. "feature_fan_feature_final":
  245. {
  246. "label": "Final %",
  247. "description": "If you choose an 'End Layer' then this is the fan speed that will carry through to the end of the gcode file. It will go into effect at the 'END' of your End layer.",
  248. "type": "int",
  249. "default_value": 35,
  250. "minimum_value": 0,
  251. "maximum_value": 100,
  252. "unit": "% ",
  253. "enabled": "(int(feature_fan_end_layer) != -1) and (fan_layer_or_feature == 'by_feature')"
  254. },
  255. "fan_enable_raft":
  256. {
  257. "label": "Enable Raft Cooling",
  258. "description": "Enable the fan for the raft layers. When enabled the Raft Fan Speed will continue until another Layer or Feature setting over-rides it.",
  259. "type": "bool",
  260. "default_value": false,
  261. "enabled": true
  262. },
  263. "fan_raft_percent":
  264. {
  265. "label": "Raft Fan %:",
  266. "description": "Enter the percentage for the Raft.",
  267. "type": "int",
  268. "default_value": 35,
  269. "minimum_value": 0,
  270. "maximum_value": 100,
  271. "unit": "% ",
  272. "enabled": "fan_enable_raft"
  273. }
  274. }
  275. }"""
  276. def initialize(self) -> None:
  277. super().initialize()
  278. scripts = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("post_processing_scripts")
  279. if scripts != None:
  280. script_count = scripts.count("AddCoolingProfile")
  281. if script_count > 0:
  282. ## Set 'Remove M106 lines' to "false" if there is already an instance of this script running.
  283. self._instance.setProperty("delete_existing_m106", "value", False)
  284. def execute(self, data):
  285. #Initialize variables that are buried in if statements.
  286. mycura = Application.getInstance().getGlobalContainerStack()
  287. t0_fan = " P0"; t1_fan = " P0"; t2_fan = " P0"; t3_fan = " P0"; is_multi_extr_print = True
  288. #Get some information from Cura-----------------------------------
  289. extruder = mycura.extruderList
  290. #This will be true when fan scale is 0-255pwm and false when it's RepRap 0-1 (Cura 5.x)
  291. fan_mode = True
  292. ##For 4.x versions that don't have the 0-1 option
  293. try:
  294. fan_mode = not bool(extruder[0].getProperty("machine_scale_fan_speed_zero_to_one", "value"))
  295. except:
  296. pass
  297. bed_adhesion = (extruder[0].getProperty("adhesion_type", "value"))
  298. extruder_count = mycura.getProperty("machine_extruder_count", "value")
  299. print_sequence = str(mycura.getProperty("print_sequence", "value"))
  300. #Assign the fan numbers to the tools------------------------------
  301. if extruder_count == 1:
  302. is_multi_fan = False
  303. is_multi_extr_print = False
  304. if int((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value"))) > 0:
  305. t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
  306. else:
  307. #No P parameter if there is a single fan circuit------------------
  308. t0_fan = ""
  309. #Get the cooling fan numbers for each extruder if the printer has multiple extruders
  310. elif extruder_count > 1:
  311. is_multi_fan = True
  312. t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
  313. if is_multi_fan:
  314. if extruder_count > 1: t1_fan = " P" + str((extruder[1].getProperty("machine_extruder_cooling_fan_number", "value")))
  315. if extruder_count > 2: t2_fan = " P" + str((extruder[2].getProperty("machine_extruder_cooling_fan_number", "value")))
  316. if extruder_count > 3: t3_fan = " P" + str((extruder[3].getProperty("machine_extruder_cooling_fan_number", "value")))
  317. #Initialize the fan_list with defaults----------------------------
  318. fan_list = ["z"] * 16
  319. for num in range(0,15,2):
  320. fan_list[num] = len(data)
  321. fan_list[num + 1] = "M106 S0"
  322. #Assign the variable values if "By Layer"-------------------------
  323. by_layer_or_feature = self.getSettingValueByKey("fan_layer_or_feature")
  324. if by_layer_or_feature == "by_layer":
  325. ## By layer doesn't do any feature search so there is no need to look for combing moves
  326. feature_fan_combing = False
  327. fan_list[0] = self.getSettingValueByKey("layer_fan_1")
  328. fan_list[2] = self.getSettingValueByKey("layer_fan_2")
  329. fan_list[4] = self.getSettingValueByKey("layer_fan_3")
  330. fan_list[6] = self.getSettingValueByKey("layer_fan_4")
  331. fan_list[8] = self.getSettingValueByKey("layer_fan_5")
  332. fan_list[10] = self.getSettingValueByKey("layer_fan_6")
  333. fan_list[12] = self.getSettingValueByKey("layer_fan_7")
  334. fan_list[14] = self.getSettingValueByKey("layer_fan_8")
  335. ## If there is no '/' delimiter then ignore the line else put the settings in a list
  336. for num in range(0,15,2):
  337. if "/" in fan_list[num]:
  338. fan_list[num + 1] = self._layer_checker(fan_list[num], "p", fan_mode)
  339. fan_list[num] = self._layer_checker(fan_list[num], "l", fan_mode)
  340. ## Assign the variable values if "By Feature"
  341. elif by_layer_or_feature == "by_feature":
  342. the_start_layer = self.getSettingValueByKey("feature_fan_start_layer") - 1
  343. the_end_layer = self.getSettingValueByKey("feature_fan_end_layer")
  344. try:
  345. if int(the_end_layer) != -1:
  346. ## Catch a possible input error.
  347. if the_end_layer < the_start_layer:
  348. the_end_layer = the_start_layer
  349. except:
  350. the_end_layer = -1 ## If there is an input error default to the entire gcode file.
  351. ## Get the speed for each feature
  352. feature_name_list = []
  353. feature_speed_list = []
  354. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_skirt"), fan_mode)); feature_name_list.append(";TYPE:SKIRT")
  355. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_wall_inner"), fan_mode)); feature_name_list.append(";TYPE:WALL-INNER")
  356. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_wall_outer"), fan_mode)); feature_name_list.append(";TYPE:WALL-OUTER")
  357. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_fill"), fan_mode)); feature_name_list.append(";TYPE:FILL")
  358. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_skin"), fan_mode)); feature_name_list.append(";TYPE:SKIN")
  359. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_support"), fan_mode)); feature_name_list.append(";TYPE:SUPPORT")
  360. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_support_interface"), fan_mode)); feature_name_list.append(";TYPE:SUPPORT-INTERFACE")
  361. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_prime_tower"), fan_mode)); feature_name_list.append(";TYPE:PRIME-TOWER")
  362. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_bridge"), fan_mode)); feature_name_list.append(";BRIDGE")
  363. feature_speed_list.append(self._feature_checker(self.getSettingValueByKey("feature_fan_feature_final"), fan_mode)); feature_name_list.append("FINAL_FAN")
  364. feature_fan_combing = self.getSettingValueByKey("feature_fan_combing")
  365. if the_end_layer > -1 and by_layer_or_feature == "by_feature":
  366. ## Required so the final speed input can be determined
  367. the_end_is_enabled = True
  368. else:
  369. ## There is no ending layer so do the whole file
  370. the_end_is_enabled = False
  371. if the_end_layer == -1 or the_end_is_enabled == False:
  372. the_end_layer = len(data) + 2
  373. ## Find the Layer0Index and the RaftIndex
  374. raft_start_index = 0
  375. number_of_raft_layers = 0
  376. layer_0_index = 0
  377. ## Catch the number of raft layers.
  378. for l_num in range(1,10,1):
  379. layer = data[l_num]
  380. if ";LAYER:-" in layer:
  381. number_of_raft_layers += 1
  382. if raft_start_index == 0:
  383. raft_start_index = l_num
  384. if ";LAYER:0" in layer:
  385. layer_0_index = l_num
  386. break
  387. ## Is this a single extruder print on a multi-extruder printer? - get the correct fan number for the extruder being used.
  388. if is_multi_fan:
  389. T0_used = False
  390. T1_used = False
  391. T2_used = False
  392. T3_used = False
  393. ## Bypass the file header and ending gcode.
  394. for num in range(1,len(data)-1,1):
  395. lines = data[num]
  396. if "T0" in lines:
  397. T0_used = True
  398. if "T1" in lines:
  399. T1_used = True
  400. if "T2" in lines:
  401. T2_used = True
  402. if "T3" in lines:
  403. T3_used = True
  404. is_multi_extr_print = True if sum([T0_used, T1_used, T2_used, T3_used]) > 1 else False
  405. ## On a multi-extruder printer and single extruder print find out which extruder starts the file.
  406. init_fan = t0_fan
  407. if not is_multi_extr_print:
  408. startup = data[1]
  409. lines = startup.split("\n")
  410. for line in lines:
  411. if line == "T1":
  412. t0_fan = t1_fan
  413. elif line == "T2":
  414. t0_fan = t2_fan
  415. elif line == "T3":
  416. t0_fan = t3_fan
  417. elif is_multi_extr_print:
  418. ## On a multi-extruder printer and multi extruder print find out which extruder starts the file.
  419. startup = data[1]
  420. lines = startup.split("\n")
  421. for line in lines:
  422. if line == "T0":
  423. init_fan = t0_fan
  424. elif line == "T1":
  425. init_fan = t1_fan
  426. elif line == "T2":
  427. init_fan = t2_fan
  428. elif line == "T3":
  429. init_fan = t3_fan
  430. else:
  431. init_fan = ""
  432. ## Assign the variable values if "Raft Enabled"
  433. raft_enabled = self.getSettingValueByKey("fan_enable_raft")
  434. if raft_enabled and bed_adhesion == "raft":
  435. fan_sp_raft = self._feature_checker(self.getSettingValueByKey("fan_raft_percent"), fan_mode)
  436. else:
  437. fan_sp_raft = "M106 S0"
  438. # Start to alter the data-----------------------------------------
  439. ## Strip the existing M106 lines from the file up to the end of the last layer. If a user wants to use more than one instance of this plugin then they won't want to erase the M106 lines that the preceding plugins inserted so 'delete_existing_m106' is an option.
  440. delete_existing_m106 = self.getSettingValueByKey("delete_existing_m106")
  441. if delete_existing_m106:
  442. ## Start deleting from the beginning
  443. start_from = int(raft_start_index)
  444. else:
  445. if by_layer_or_feature == "by_layer":
  446. altered_start_layer = str(len(data))
  447. ## The fan list layers don't need to be in ascending order. Get the lowest.
  448. for num in range(0,15,2):
  449. try:
  450. if int(fan_list[num]) < int(altered_start_layer):
  451. altered_start_layer = int(fan_list[num])
  452. except:
  453. pass
  454. elif by_layer_or_feature == "by_feature":
  455. altered_start_layer = int(the_start_layer) - 1
  456. start_from = int(layer_0_index) + int(altered_start_layer)
  457. ## Strip the M106 and M107 lines from the file
  458. for l_index in range(int(start_from), len(data) - 1, 1):
  459. data[l_index] = re.sub(re.compile("M106(.*)\n"), "", data[l_index])
  460. data[l_index] = re.sub(re.compile("M107(.*)\n"), "", data[l_index])
  461. ## Deal with a raft and with One-At-A-Time print sequence
  462. if raft_enabled and bed_adhesion == "raft":
  463. if print_sequence == "one_at_a_time":
  464. for r_index in range(2,len(data)-2,1):
  465. lines = data[r_index].split("\n")
  466. if not raft_enabled or bed_adhesion != "raft":
  467. if ";LAYER:0" in data[r_index] or ";LAYER:-" in data[r_index]:
  468. lines.insert(1, "M106 S0" + str(t0_fan))
  469. if raft_enabled and bed_adhesion == "raft":
  470. if ";LAYER:-" in data[r_index]:
  471. ## Turn the raft fan on
  472. lines.insert(1, fan_sp_raft + str(t0_fan))
  473. ## Shut the raft fan off at layer 0
  474. if ";LAYER:0" in data[r_index]:
  475. lines.insert(1,"M106 S0" + str(t0_fan))
  476. data[r_index] = "\n".join(lines)
  477. elif print_sequence == "all_at_once":
  478. layer = data[raft_start_index]
  479. lines = layer.split("\n")
  480. if ";LAYER:-" in layer:
  481. ## Turn the raft fan on
  482. lines.insert(1, fan_sp_raft + str(init_fan))
  483. layer = "\n".join(lines)
  484. data[raft_start_index] = layer
  485. layer = data[layer_0_index]
  486. lines = layer.split("\n")
  487. ## Shut the raft fan off
  488. lines.insert(1, "M106 S0" + str(init_fan))
  489. data[layer_0_index] = "\n".join(lines)
  490. else:
  491. for r_index in range(2,len(data)-2,1):
  492. lines = data[r_index].split("\n")
  493. if ";LAYER:0" in data[r_index] or ";LAYER:-" in data[r_index]:
  494. if not "0" in fan_list:
  495. lines.insert(1, "M106 S0" + str(t0_fan))
  496. data[r_index] = "\n".join(lines)
  497. ## Turn off all fans at the end of data[1]. If more than one instance of this script is running then this will result in multiple M106 lines.
  498. temp_startup = data[1].split("\n")
  499. temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t0_fan))
  500. ## If there are multiple cooling fans shut them all off
  501. if is_multi_fan:
  502. if extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan))
  503. if extruder_count > 2 and t2_fan != t1_fan and t2_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t2_fan))
  504. if extruder_count > 3 and t3_fan != t2_fan and t3_fan != t1_fan and t3_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t3_fan))
  505. data[1] = "\n".join(temp_startup)
  506. ## If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long
  507. ## For compatibility with 5.3.0 change any MESH:NOMESH to MESH:NONMESH.
  508. if feature_fan_combing:
  509. for layer_num in range(2,len(data)):
  510. layer = data[layer_num]
  511. data[layer_num] = re.sub(";MESH:NOMESH", ";MESH:NONMESH", layer)
  512. data = self._add_travel_comment(data, layer_0_index)
  513. # Single Fan "By Layer"--------------------------------------------
  514. if by_layer_or_feature == "by_layer" and not is_multi_fan:
  515. return self._single_fan_by_layer(data, layer_0_index, fan_list, t0_fan)
  516. # Multi-Fan "By Layer"---------------------------------------------
  517. if by_layer_or_feature == "by_layer" and is_multi_fan:
  518. return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan)
  519. #Single Fan "By Feature"------------------------------------------
  520. if by_layer_or_feature == "by_feature" and (not is_multi_fan or not is_multi_extr_print):
  521. return self._single_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, feature_speed_list, feature_name_list, feature_fan_combing)
  522. #Multi Fan "By Feature"-------------------------------------------
  523. if by_layer_or_feature == "by_feature" and is_multi_fan:
  524. return self._multi_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, t1_fan, t2_fan, t3_fan, feature_speed_list, feature_name_list, feature_fan_combing)
  525. # The Single Fan "By Layer"----------------------------------------
  526. def _single_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str)->str:
  527. layer_number = "0"
  528. single_fan_data = data
  529. for l_index in range(layer_0_index,len(single_fan_data)-1,1):
  530. layer = single_fan_data[l_index]
  531. fan_lines = layer.split("\n")
  532. for fan_line in fan_lines:
  533. if ";LAYER:" in fan_line:
  534. layer_number = str(fan_line.split(":")[1])
  535. ## If there is a match for the current layer number make the insertion
  536. for num in range(0,15,2):
  537. if layer_number == str(fan_list[num]):
  538. layer = layer.replace(fan_lines[0],fan_lines[0] + "\n" + fan_list[num + 1] + str(t0_fan))
  539. single_fan_data[l_index] = layer
  540. return single_fan_data
  541. # Multi-Fan "By Layer"-----------------------------------------
  542. def _multi_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str)->str:
  543. multi_fan_data = data
  544. layer_number = "0"
  545. current_fan_speed = "0"
  546. prev_fan = str(t0_fan)
  547. this_fan = str(t0_fan)
  548. start_index = str(len(multi_fan_data))
  549. for num in range(0,15,2):
  550. ## The fan_list may not be in ascending order. Get the lowest layer number
  551. try:
  552. if int(fan_list[num]) < int(start_index):
  553. start_index = str(fan_list[num])
  554. except:
  555. pass
  556. ## Move the start point if delete_existing_m106 is false
  557. start_index = int(start_index) + int(layer_0_index)
  558. ## Track the tool number
  559. for num in range(1,int(start_index),1):
  560. layer = multi_fan_data[num]
  561. lines = layer.split("\n")
  562. for line in lines:
  563. if line == "T0":
  564. prev_fan = this_fan
  565. this_fan = t0_fan
  566. elif line == "T1":
  567. prev_fan = this_fan
  568. this_fan = t1_fan
  569. elif line == "T2":
  570. prev_fan = this_fan
  571. this_fan = t2_fan
  572. elif line == "T3":
  573. prev_fan = this_fan
  574. this_fan = t3_fan
  575. for l_index in range(int(start_index),len(multi_fan_data)-1,1):
  576. modified_data = ""
  577. layer = multi_fan_data[l_index]
  578. fan_lines = layer.split("\n")
  579. for fan_line in fan_lines:
  580. ## Prepare to shut down the previous fan and start the next one.
  581. if fan_line.startswith("T"):
  582. if fan_line == "T0": this_fan = str(t0_fan)
  583. if fan_line == "T1": this_fan = str(t1_fan)
  584. if fan_line == "T2": this_fan = str(t2_fan)
  585. if fan_line == "T3": this_fan = str(t3_fan)
  586. modified_data += "M106 S0" + prev_fan + "\n"
  587. modified_data += fan_line + "\n"
  588. modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
  589. prev_fan = this_fan
  590. elif ";LAYER:" in fan_line:
  591. modified_data += fan_line + "\n"
  592. layer_number = str(fan_line.split(":")[1])
  593. for num in range(0,15,2):
  594. if layer_number == str(fan_list[num]):
  595. modified_data += fan_list[num + 1] + this_fan + "\n"
  596. current_fan_speed = str(fan_list[num + 1].split("S")[1])
  597. current_fan_speed = str(current_fan_speed.split(" ")[0]) ## Just in case
  598. else:
  599. modified_data += fan_line + "\n"
  600. if modified_data.endswith("\n"): modified_data = modified_data[0:-1]
  601. multi_fan_data[l_index] = modified_data
  602. return multi_fan_data
  603. # Single fan by feature-----------------------------------------------
  604. def _single_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str:
  605. single_fan_data = data
  606. layer_number = "0"
  607. index = 1
  608. ## Start with layer:0
  609. for l_index in range(layer_0_index,len(single_fan_data)-1,1):
  610. modified_data = ""
  611. layer = single_fan_data[l_index]
  612. lines = layer.split("\n")
  613. for line in lines:
  614. if ";LAYER:" in line:
  615. layer_number = str(line.split(":")[1])
  616. if int(layer_number) >= int(the_start_layer) and int(layer_number) < int(the_end_layer)-1:
  617. temp = line.split(" ")[0]
  618. try:
  619. name_index = feature_name_list.index(temp)
  620. except:
  621. name_index = -1
  622. if name_index != -1:
  623. modified_data += feature_speed_list[name_index] + t0_fan + "\n"
  624. elif ";MESH:NONMESH" in line:
  625. if feature_fan_combing == True:
  626. modified_data += "M106 S0" + t0_fan + "\n"
  627. modified_data += line + "\n"
  628. ## If an End Layer is defined and is less than the last layer then insert the Final Speed
  629. if line == ";LAYER:" + str(the_end_layer) and the_end_is_enabled == True:
  630. modified_data += feature_speed_list[len(feature_speed_list) - 1] + t0_fan + "\n"
  631. if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
  632. single_fan_data[l_index] = modified_data
  633. return single_fan_data
  634. # Multi-fan by feature------------------------------------------------
  635. def _multi_fan_by_feature(self, data: str, layer_0_index: int, the_start_layer: str, the_end_layer: str, the_end_is_enabled: str, fan_list: str, t0_fan: str, t1_fan: str, t2_fan: str, t3_fan: str, feature_speed_list: str, feature_name_list: str, feature_fan_combing: bool)->str:
  636. multi_fan_data = data
  637. layer_number = "0"
  638. start_index = 1
  639. prev_fan = t0_fan
  640. this_fan = t0_fan
  641. modified_data = ""
  642. current_fan_speed = "0"
  643. for my_index in range(1, len(multi_fan_data) - 1, 1):
  644. layer = multi_fan_data[my_index]
  645. if ";LAYER:" + str(the_start_layer) + "\n" in layer:
  646. start_index = int(my_index) - 1
  647. break
  648. ## Track the previous tool changes
  649. for num in range(1,start_index,1):
  650. layer = multi_fan_data[num]
  651. lines = layer.split("\n")
  652. for line in lines:
  653. if line == "T0":
  654. prev_fan = this_fan
  655. this_fan = t0_fan
  656. elif line == "T1":
  657. prev_fan = this_fan
  658. this_fan = t1_fan
  659. elif line == "T2":
  660. prev_fan = this_fan
  661. this_fan = t2_fan
  662. elif line == "T3":
  663. prev_fan = this_fan
  664. this_fan = t3_fan
  665. ## Get the current tool.
  666. for l_index in range(start_index,start_index + 1,1):
  667. layer = multi_fan_data[l_index]
  668. lines = layer.split("\n")
  669. for line in lines:
  670. if line.startswith("T"):
  671. if line == "T0": this_fan = t0_fan
  672. if line == "T1": this_fan = t1_fan
  673. if line == "T2": this_fan = t2_fan
  674. if line == "T3": this_fan = t3_fan
  675. prev_fan = this_fan
  676. ## Start to make insertions-------------------------------------
  677. for l_index in range(start_index+1,len(multi_fan_data)-1,1):
  678. layer = multi_fan_data[l_index]
  679. lines = layer.split("\n")
  680. for line in lines:
  681. if line.startswith("T"):
  682. if line == "T0": this_fan = t0_fan
  683. if line == "T1": this_fan = t1_fan
  684. if line == "T2": this_fan = t2_fan
  685. if line == "T3": this_fan = t3_fan
  686. ## Turn off the prev fan
  687. modified_data += "M106 S0" + prev_fan + "\n"
  688. modified_data += line + "\n"
  689. ## Turn on the current fan
  690. modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
  691. prev_fan = this_fan
  692. if ";LAYER:" in line:
  693. layer_number = str(line.split(":")[1])
  694. modified_data += line + "\n"
  695. if int(layer_number) >= int(the_start_layer):
  696. temp = line.split(" ")[0]
  697. try:
  698. name_index = feature_name_list.index(temp)
  699. except:
  700. name_index = -1
  701. if name_index != -1:
  702. modified_data += line + "\n" + feature_speed_list[name_index] + this_fan + "\n"
  703. #modified_data += feature_speed_list[name_index] + this_fan + "\n"
  704. current_fan_speed = str(feature_speed_list[name_index].split("S")[1])
  705. elif ";MESH:NONMESH" in line:
  706. if feature_fan_combing == True:
  707. modified_data += line + "\n"
  708. modified_data += "M106 S0" + this_fan + "\n"
  709. current_fan_speed = "0"
  710. else:
  711. modified_data += line + "\n"
  712. ## If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file
  713. ## There cannot be a break here because if there are multiple fan numbers they still need to be shut off and turned on.
  714. elif line == ";LAYER:" + str(the_end_layer):
  715. modified_data += feature_speed_list[len(feature_speed_list) - 1] + this_fan + "\n"
  716. for set_speed in range(0, len(feature_speed_list) - 2):
  717. feature_speed_list[set_speed] = feature_speed_list[len(feature_speed_list) - 1]
  718. else:
  719. ## Layer and Tool get inserted into modified_data above. All other lines go into modified_data here
  720. if not line.startswith("T") and not line.startswith(";LAYER:"): modified_data += line + "\n"
  721. if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
  722. multi_fan_data[l_index] = modified_data
  723. modified_data = ""
  724. return multi_fan_data
  725. #Try to catch layer input errors, set the minimum speed to 12%, and put the strings together
  726. def _layer_checker(self, fan_string: str, ty_pe: str, fan_mode: bool) -> str:
  727. fan_string_l = str(fan_string.split("/")[0])
  728. try:
  729. if int(fan_string_l) <= 1: fan_string_l = "1"
  730. if fan_string_l == "": fan_string_l = str(len(data))
  731. except ValueError:
  732. fan_string_l = str(len(data))
  733. fan_string_l = str(int(fan_string_l) - 1)
  734. fan_string_p = str(fan_string.split("/")[1])
  735. if fan_string_p == "": fan_string_p = "0"
  736. try:
  737. if int(fan_string_p) < 0: fan_string_p = "0"
  738. if int(fan_string_p) > 100: fan_string_p = "100"
  739. except ValueError:
  740. fan_string_p = "0"
  741. ## Set the minimum fan speed to 12%
  742. if int(fan_string_p) < 12 and int(fan_string_p) != 0:
  743. fan_string_p = "12"
  744. fan_layer_line = str(fan_string_l)
  745. if fan_mode:
  746. fan_percent_line = "M106 S" + str(round(int(fan_string_p) * 2.55))
  747. else:
  748. fan_percent_line = "M106 S" + str(round(int(fan_string_p) / 100, 1))
  749. if ty_pe == "l":
  750. return str(fan_layer_line)
  751. elif ty_pe == "p":
  752. return fan_percent_line
  753. #Try to catch feature input errors, set the minimum speed to 12%, and put the strings together when 'By Feature'
  754. def _feature_checker(self, fan_feat_string: int, fan_mode: bool) -> str:
  755. if fan_feat_string < 0: fan_feat_string = 0
  756. ## Set the minimum fan speed to 12%
  757. if fan_feat_string > 0 and fan_feat_string < 12: fan_feat_string = 12
  758. if fan_feat_string > 100: fan_feat_string = 100
  759. if fan_mode:
  760. fan_sp_feat = "M106 S" + str(round(fan_feat_string * 2.55))
  761. else:
  762. fan_sp_feat = "M106 S" + str(round(fan_feat_string / 100, 1))
  763. return fan_sp_feat
  764. # Add additional travel comments to turn the fan off during combing.
  765. def _add_travel_comment(self, comment_data: str, lay_0_index: str) -> str:
  766. for lay_num in range(int(lay_0_index), len(comment_data)-1,1):
  767. layer = comment_data[lay_num]
  768. lines = layer.split("\n")
  769. ## Copy the data to new_data and make the insertions there
  770. new_data = lines
  771. g0_count = 0
  772. g0_index = -1
  773. feature_type = ";TYPE:SUPPORT"
  774. is_travel = False
  775. for index, line in enumerate(lines):
  776. insert_index = 0
  777. if ";TYPE:" in line:
  778. feature_type = line
  779. is_travel = False
  780. g0_count = 0
  781. if ";MESH:NONMESH" in line:
  782. is_travel = True
  783. g0_count = 0
  784. if line.startswith("G0 ") and not is_travel:
  785. g0_count += 1
  786. if g0_index == -1:
  787. g0_index = lines.index(line)
  788. elif not line.startswith("G0 ") and not is_travel:
  789. ## Add additional 'NONMESH' lines to shut the fan off during long combing moves--------
  790. if g0_count > 5:
  791. if not is_travel:
  792. new_data.insert(g0_index + insert_index, ";MESH:NONMESH")
  793. insert_index += 1
  794. ## Add the feature_type at the end of the combing move to turn the fan back on
  795. new_data.insert(g0_index + g0_count + 1, feature_type)
  796. insert_index += 1
  797. g0_count = 0
  798. g0_index = -1
  799. is_travel = False
  800. elif g0_count <= 5:
  801. g0_count = 0
  802. g0_index = -1
  803. is_travel = False
  804. comment_data[lay_num] = "\n".join(new_data)
  805. return comment_data