AddCoolingProfile.py 47 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  1. # January 2023 by GregValiant (Greg Foresi).
  2. # Functions:
  3. # Remove all fan speed lines from the file (optional).
  4. # Enter new M106 lines "By Layer" or "By Feature" (;TYPE:WALL-OUTER, etc.).
  5. # A Starting layer and/or an Ending layer can be defined.
  6. # Fan speeds are scaled PWM (0 - 255) or RepRap (0 - 1) depending on {machine_scale_fan_speed_zero_to_one}.
  7. # A minimum fan speed of 12% is enforced.
  8. # If multiple extruders have separate fan circuits the speeds are set at tool changes and conform to the layer or
  9. # feature setting. There is support for up to 4 layer cooling fan circuits.
  10. # The option for whether or not to remove the existing M106 lines is added to allow multiple instances of this post-processor to be installed without the followup instances wiping out the insertions of previous instances. 1/3 of a file can be 'By Layer' and the second third 'By Feature', and end up with 'By Layer' again.
  11. # My design intent was to make it as full featured and "industrial strength" as I could.
  12. # My thanks to @5axes, @fieldOfView(@AHoeben), @Ghostkeeper, and @Torgeir.
  13. # 9/14/23 added support for One-at-a-Time and removed the kick out code
  14. # 12/15/23 split off some functions. Revised the regex replacements.
  15. from ..Script import Script
  16. from UM.Application import Application
  17. import re
  18. class AddCoolingProfile(Script):
  19. def getSettingDataString(self):
  20. return """{
  21. "name": "Advanced Cooling Fan Control",
  22. "key": "AddCoolingProfile",
  23. "metadata": {},
  24. "version": 2,
  25. "settings":
  26. {
  27. "fan_layer_or_feature":
  28. {
  29. "label": "Cooling Control by:",
  30. "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.",
  31. "type": "enum",
  32. "options": {
  33. "by_layer": "Layer Numbers",
  34. "by_feature": "Feature Types"},
  35. "default_value": "by_layer"
  36. },
  37. "delete_existing_m106":
  38. {
  39. "label": "Remove M106 lines prior to inserting new.",
  40. "description": "If you have 2 or more instances of 'Advanced Cooling Fan Control' running (to cool a portion of a print differently), then 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. If you want to keep the Cura inserted lines up to the point where this post-processor will start making insertions, then un-check the box.",
  41. "type": "bool",
  42. "enabled": true,
  43. "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_1st":
  66. {
  67. "label": "Layer/Percent #1",
  68. "description": "Enter as: 'LAYER / Percent' Ex: 57/100 with the layer first, then a '/' to delimit, and then the fan percentage.",
  69. "type": "str",
  70. "default_value": "5/30",
  71. "unit": "L#/% ",
  72. "enabled": "fan_layer_or_feature == 'by_layer'"
  73. },
  74. "layer_fan_2nd":
  75. {
  76. "label": "Layer/Percent #2",
  77. "description": "Enter as: 'LAYER / Percent' Ex: 57/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_3rd":
  84. {
  85. "label": "Layer/Percent #3",
  86. "description": "Enter as: 'LAYER / Percent' Ex: 57/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_4th":
  93. {
  94. "label": "Layer/Percent #4",
  95. "description": "Enter as: 'LAYER / Percent' Ex: 57/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_5th":
  102. {
  103. "label": "Layer/Percent #5",
  104. "description": "Enter as: 'LAYER / Percent' Ex: 57/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_6th":
  111. {
  112. "label": "Layer/Percent #6",
  113. "description": "Enter as: 'LAYER / Percent' Ex: 57/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_7th":
  120. {
  121. "label": "Layer/Percent #7",
  122. "description": "Enter as: 'LAYER / Percent' Ex: 57/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_8th":
  129. {
  130. "label": "Layer/Percent #8",
  131. "description": "Enter as: 'LAYER / Percent' Ex: 57/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 the default to "false" if there is more than one 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. elif extruder_count > 1:
  310. is_multi_fan = True
  311. t0_fan = " P" + str((extruder[0].getProperty("machine_extruder_cooling_fan_number", "value")))
  312. if is_multi_fan:
  313. if extruder_count > 1: t1_fan = " P" + str((extruder[1].getProperty("machine_extruder_cooling_fan_number", "value")))
  314. if extruder_count > 2: t2_fan = " P" + str((extruder[2].getProperty("machine_extruder_cooling_fan_number", "value")))
  315. if extruder_count > 3: t3_fan = " P" + str((extruder[3].getProperty("machine_extruder_cooling_fan_number", "value")))
  316. #Initialize the fan_list with defaults----------------------------
  317. fan_list = ["z"] * 16
  318. for num in range(0,15,2):
  319. fan_list[num + 1] = "M106 S0"
  320. #Assign the variable values if "By Layer"-------------------------
  321. by_layer_or_feature = self.getSettingValueByKey("fan_layer_or_feature")
  322. if by_layer_or_feature == "by_layer":
  323. ## By layer doesn't do any feature search
  324. feature_fan_combing = False
  325. fan_list[0] = self.getSettingValueByKey("layer_fan_1st")
  326. fan_list[2] = self.getSettingValueByKey("layer_fan_2nd")
  327. fan_list[4] = self.getSettingValueByKey("layer_fan_3rd")
  328. fan_list[6] = self.getSettingValueByKey("layer_fan_4th")
  329. fan_list[8] = self.getSettingValueByKey("layer_fan_5th")
  330. fan_list[10] = self.getSettingValueByKey("layer_fan_6th")
  331. fan_list[12] = self.getSettingValueByKey("layer_fan_7th")
  332. fan_list[14] = self.getSettingValueByKey("layer_fan_8th")
  333. ## If there is no '/' delimiter then ignore the line else put the settings in a list
  334. for num in range(0,15,2):
  335. if "/" in fan_list[num]:
  336. fan_list[num + 1] = self._layer_checker(fan_list[num], "p", fan_mode)
  337. fan_list[num] = self._layer_checker(fan_list[num], "l", fan_mode)
  338. ## Assign the variable values if "By Feature"
  339. elif by_layer_or_feature == "by_feature":
  340. the_start_layer = self.getSettingValueByKey("feature_fan_start_layer") - 1
  341. the_end_layer = self.getSettingValueByKey("feature_fan_end_layer")
  342. try:
  343. if int(the_end_layer) != -1:
  344. ## Catch a possible input error.
  345. if the_end_layer < the_start_layer:
  346. the_end_layer = the_start_layer
  347. except:
  348. the_end_layer = -1 ## If there is an input error default to the entire gcode file.
  349. ## Get the speed for each feature
  350. # 0;TYPE:SKIRT, 1;TYPE:WALL-INNER, 2;TYPE:WALL-OUTER, 3;TYPE:FILL, 4;TYPE:SKIN, 5;TYPE:SUPPORT, 6;TYPE:SUPPORT-INTERFACE, 7;TYPE:PRIME-TOWER, 8;BRIDGE, 9;FEATURE_FINAL, 10;FAN_COMBING,
  351. fan_sp_skirt = self._feature_checker(self.getSettingValueByKey("feature_fan_skirt"), fan_mode)
  352. fan_sp_wall_inner = self._feature_checker(self.getSettingValueByKey("feature_fan_wall_inner"), fan_mode)
  353. fan_sp_wall_outer = self._feature_checker(self.getSettingValueByKey("feature_fan_wall_outer"), fan_mode)
  354. fan_sp_fill = self._feature_checker(self.getSettingValueByKey("feature_fan_fill"), fan_mode)
  355. fan_sp_skin = self._feature_checker(self.getSettingValueByKey("feature_fan_skin"), fan_mode)
  356. fan_sp_support = self._feature_checker(self.getSettingValueByKey("feature_fan_support"), fan_mode)
  357. fan_sp_support_interface = self._feature_checker(self.getSettingValueByKey("feature_fan_support_interface"), fan_mode)
  358. fan_sp_prime_tower = self._feature_checker(self.getSettingValueByKey("feature_fan_prime_tower"), fan_mode)
  359. fan_sp_bridge = self._feature_checker(self.getSettingValueByKey("feature_fan_bridge"), fan_mode)
  360. fan_sp_feature_final = self._feature_checker(self.getSettingValueByKey("feature_fan_feature_final"), fan_mode)
  361. feature_fan_combing = self.getSettingValueByKey("feature_fan_combing")
  362. if the_end_layer > -1 and by_layer_or_feature == "by_feature":
  363. ## Required so the final speed input can be determined
  364. the_end_is_enabled = True
  365. else:
  366. ## There is no ending layer so do the whole file
  367. the_end_is_enabled = False
  368. if the_end_layer == -1 or the_end_is_enabled == False:
  369. the_end_layer = len(data) + 2
  370. ## Find the Layer0Index and the RaftIndex
  371. raft_start_index = 0
  372. number_of_raft_layers = 0
  373. layer_0_index = 0
  374. ## Catch the number of raft layers.
  375. for l_num in range(1,10,1):
  376. layer = data[l_num]
  377. if ";LAYER:-" in layer:
  378. number_of_raft_layers += 1
  379. if raft_start_index == 0:
  380. raft_start_index = l_num
  381. if ";LAYER:0" in layer:
  382. layer_0_index = l_num
  383. break
  384. ## Is this a single extruder print on a multi-extruder printer? - get the correct fan number for the extruder being used.
  385. if is_multi_fan:
  386. T0_used = False
  387. T1_used = False
  388. T2_used = False
  389. T3_used = False
  390. ## Bypass the file header and ending gcode.
  391. for num in range(1,len(data)-1,1):
  392. lines = data[num]
  393. if "T0" in lines:
  394. T0_used = True
  395. if "T1" in lines:
  396. T1_used = True
  397. if "T2" in lines:
  398. T2_used = True
  399. if "T3" in lines:
  400. T3_used = True
  401. is_multi_extr_print = True if sum([T0_used, T1_used, T2_used, T3_used]) > 1 else False
  402. ## On a multi-extruder printer and single extruder print find out which extruder starts the file.
  403. init_fan = t0_fan
  404. if not is_multi_extr_print:
  405. startup = data[1]
  406. lines = startup.split("\n")
  407. for line in lines:
  408. if line == "T1":
  409. t0_fan = t1_fan
  410. elif line == "T2":
  411. t0_fan = t2_fan
  412. elif line == "T3":
  413. t0_fan = t3_fan
  414. elif is_multi_extr_print:
  415. ## On a multi-extruder printer and multi extruder print find out which extruder starts the file.
  416. startup = data[1]
  417. lines = startup.split("\n")
  418. for line in lines:
  419. if line == "T0":
  420. init_fan = t0_fan
  421. elif line == "T1":
  422. init_fan = t1_fan
  423. elif line == "T2":
  424. init_fan = t2_fan
  425. elif line == "T3":
  426. init_fan = t3_fan
  427. else:
  428. init_fan = ""
  429. ## Assign the variable values if "Raft Enabled"
  430. raft_enabled = self.getSettingValueByKey("fan_enable_raft")
  431. if raft_enabled and bed_adhesion == "raft":
  432. fan_sp_raft = self._feature_checker(self.getSettingValueByKey("fan_raft_percent"), fan_mode)
  433. else:
  434. fan_sp_raft = "M106 S0"
  435. # Start to alter the data-----------------------------------------
  436. ## 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.
  437. delete_existing_m106 = self.getSettingValueByKey("delete_existing_m106")
  438. if delete_existing_m106:
  439. ## Start deleting from the beginning
  440. start_from = int(raft_start_index)
  441. else:
  442. if by_layer_or_feature == "by_layer":
  443. altered_start_layer = str(len(data))
  444. ## The fan list layers don't need to be in ascending order. Get the lowest.
  445. for num in range(0,15,2):
  446. if int(fan_list[num]) < int(altered_start_layer):
  447. altered_start_layer = int(fan_list[num])
  448. elif by_layer_or_feature == "by_feature":
  449. altered_start_layer = int(the_start_layer) - 1
  450. start_from = int(layer_0_index) + int(altered_start_layer)
  451. ## Strip the M106 and M107 lines from the file
  452. for l_index in range(int(start_from), len(data) - 1, 1):
  453. data[l_index] = re.sub(re.compile("M106(.*)\n"), "", data[l_index])
  454. data[l_index] = re.sub(re.compile("M107(.*)\n"), "", data[l_index])
  455. ## Deal with a raft and with One-At-A-Time
  456. if raft_enabled and bed_adhesion == "raft":
  457. if print_sequence == "one_at_a_time":
  458. for r_index in range(2,len(data)-2,1):
  459. lines = data[r_index].split("\n")
  460. if not raft_enabled or bed_adhesion != "raft":
  461. if ";LAYER:0" in data[r_index] or ";LAYER:-" in data[r_index]:
  462. lines.insert(1, "M106 S0" + str(t0_fan))
  463. if raft_enabled and bed_adhesion == "raft":
  464. if ";LAYER:-" in data[r_index]:
  465. ## Turn the raft fan on
  466. lines.insert(1, fan_sp_raft + str(t0_fan))
  467. ## Shut the raft fan off
  468. if ";LAYER:0" in data[r_index]:
  469. lines.insert(1,"M106 S0" + str(t0_fan))
  470. data[r_index] = "\n".join(lines)
  471. elif print_sequence == "all_at_once":
  472. layer = data[raft_start_index]
  473. lines = layer.split("\n")
  474. if ";LAYER:-" in layer:
  475. ## Turn the raft fan on
  476. lines.insert(1, fan_sp_raft + str(init_fan))
  477. layer = "\n".join(lines)
  478. data[raft_start_index] = layer
  479. layer = data[layer_0_index]
  480. lines = layer.split("\n")
  481. ## Shut the raft fan off
  482. lines.insert(1, "M106 S0" + str(init_fan))
  483. data[layer_0_index] = "\n".join(lines)
  484. else:
  485. for r_index in range(2,len(data)-2,1):
  486. lines = data[r_index].split("\n")
  487. if ";LAYER:0" in data[r_index] or ";LAYER:-" in data[r_index]:
  488. if not "0" in fan_list:
  489. lines.insert(1, "M106 S0" + str(t0_fan))
  490. data[r_index] = "\n".join(lines)
  491. ## Turn off all fans at the end of data[1]
  492. temp_startup = data[1].split("\n")
  493. temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t0_fan))
  494. ## If there are multiple cooling fans shut them all off
  495. if is_multi_fan:
  496. if extruder_count > 1 and t1_fan != t0_fan: temp_startup.insert(len(temp_startup)-2,"M106 S0" + str(t1_fan))
  497. 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))
  498. 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))
  499. data[1] = "\n".join(temp_startup)
  500. ## If 'feature_fan_combing' is True then add additional 'MESH:NONMESH' lines for travel moves over 5 lines long
  501. ## For compatibility with 5.3.0 change any MESH:NOMESH to MESH:NONMESH.
  502. if feature_fan_combing:
  503. for layer_num in range(2,len(data)):
  504. layer = data[layer_num]
  505. data[layer_num] = re.sub(";MESH:NOMESH", ";MESH:NONMESH", layer)
  506. data = self._add_travel_comment(data, layer_0_index)
  507. # Single Fan "By Layer"--------------------------------------------
  508. if by_layer_or_feature == "by_layer" and not is_multi_fan:
  509. return self._single_fan_by_layer(data, layer_0_index, fan_list, t0_fan)
  510. # Multi-Fan "By Layer"---------------------------------------------
  511. if by_layer_or_feature == "by_layer" and is_multi_fan:
  512. return self._multi_fan_by_layer(data, layer_0_index, fan_list, t0_fan, t1_fan, t2_fan, t3_fan)
  513. #Single Fan "By Feature"------------------------------------------
  514. if by_layer_or_feature == "by_feature" and (not is_multi_fan or not is_multi_extr_print):
  515. return self._single_fan_by_feature(data, layer_0_index, the_start_layer, the_end_layer, the_end_is_enabled, fan_list, t0_fan, fan_sp_skirt, fan_sp_wall_inner, fan_sp_wall_outer, fan_sp_fill, fan_sp_skin, fan_sp_support, fan_sp_support_interface, feature_fan_combing, fan_sp_prime_tower, fan_sp_bridge, fan_sp_feature_final)
  516. #Multi Fan "By Feature"-------------------------------------------
  517. if by_layer_or_feature == "by_feature" and is_multi_fan:
  518. 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, fan_sp_skirt, fan_sp_wall_inner, fan_sp_wall_outer, fan_sp_fill, fan_sp_skin, fan_sp_support, fan_sp_support_interface, feature_fan_combing, fan_sp_prime_tower, fan_sp_bridge, fan_sp_feature_final)
  519. # The Single Fan "By Layer"----------------------------------------
  520. def _single_fan_by_layer(self, data: str, layer_0_index: int, fan_list: str, t0_fan: str)->str:
  521. layer_number = "0"
  522. single_fan_layer = data
  523. for l_index in range(layer_0_index,len(single_fan_layer)-1,1):
  524. layer = single_fan_layer[l_index]
  525. fan_lines = layer.split("\n")
  526. for fan_line in fan_lines:
  527. if ";LAYER:" in fan_line:
  528. layer_number = int(fan_line.split(":")[1])
  529. ## If there is a match for the current layer number make the insertion.
  530. try:
  531. for num in range(0,15,2):
  532. if int(layer_number) == int(fan_list[num]):
  533. layer = layer.replace(fan_lines[0],fan_lines[0] + "\n" + fan_list[num + 1] + str(t0_fan))
  534. single_fan_layer[l_index] = layer
  535. except:
  536. continue
  537. return single_fan_layer
  538. # Multi-Fan "By Layer"-----------------------------------------
  539. 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:
  540. multi_fan_data = data
  541. layer_number = "0"
  542. current_fan_speed = "0"
  543. prev_fan = str(t0_fan)
  544. this_fan = str(t0_fan)
  545. start_index = str(len(multi_fan_data))
  546. for num in range(0,15,2):
  547. ## The fan_list may not be in ascending order. Get the lowest layer number
  548. try:
  549. if int(fan_list[num]) < int(start_index):
  550. start_index = str(fan_list[num])
  551. except:
  552. pass
  553. ## Move the start point if delete_existing_m106 is false
  554. start_index = int(start_index) + int(layer_0_index)
  555. ## Track the tool number
  556. for num in range(1,int(start_index),1):
  557. layer = multi_fan_data[num]
  558. lines = layer.split("\n")
  559. for line in lines:
  560. if line == "T0":
  561. prev_fan = this_fan
  562. this_fan = t0_fan
  563. elif line == "T1":
  564. prev_fan = this_fan
  565. this_fan = t1_fan
  566. elif line == "T2":
  567. prev_fan = this_fan
  568. this_fan = t2_fan
  569. elif line == "T3":
  570. prev_fan = this_fan
  571. this_fan = t3_fan
  572. for l_index in range(int(start_index),len(multi_fan_data)-1,1):
  573. modified_data = ""
  574. layer = multi_fan_data[l_index]
  575. fan_lines = layer.split("\n")
  576. for fan_line in fan_lines:
  577. ## Prepare to shut down the previous fan and start the next one.
  578. if fan_line.startswith("T"):
  579. if fan_line == "T0": this_fan = str(t0_fan)
  580. if fan_line == "T1": this_fan = str(t1_fan)
  581. if fan_line == "T2": this_fan = str(t2_fan)
  582. if fan_line == "T3": this_fan = str(t3_fan)
  583. modified_data += "M106 S0" + prev_fan + "\n"
  584. modified_data += fan_line + "\n"
  585. modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
  586. prev_fan = this_fan
  587. elif ";LAYER:" in fan_line:
  588. modified_data += fan_line + "\n"
  589. layer_number = str(fan_line.split(":")[1])
  590. for num in range(0,15,2):
  591. if layer_number == str(fan_list[num]):
  592. modified_data += fan_list[num + 1] + this_fan + "\n"
  593. current_fan_speed = str(fan_list[num + 1].split("S")[1])
  594. current_fan_speed = str(current_fan_speed.split(" ")[0]) ## Just in case
  595. else:
  596. modified_data += fan_line + "\n"
  597. if modified_data.endswith("\n"): modified_data = modified_data[0:-1]
  598. multi_fan_data[l_index] = modified_data
  599. return multi_fan_data
  600. # Single fan by feature-----------------------------------------------
  601. 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, fan_sp_skirt: str, fan_sp_wall_inner: str, fan_sp_wall_outer: str, fan_sp_fill: str, fan_sp_skin: str, fan_sp_support: str, fan_sp_support_interface: str, feature_fan_combing: str, fan_sp_prime_tower: str, fan_sp_bridge: str, fan_sp_feature_final: str)->str:
  602. single_fan_data = data
  603. layer_number = "0"
  604. index = 1
  605. ## Start with layer:0
  606. for l_index in range(layer_0_index,len(single_fan_data)-1,1):
  607. modified_data = ""
  608. layer = single_fan_data[l_index]
  609. lines = layer.split("\n")
  610. for line in lines:
  611. if ";LAYER:" in line:
  612. layer_number = str(line.split(":")[1])
  613. if int(layer_number) >= int(the_start_layer) and int(layer_number) < int(the_end_layer)-1:
  614. if ";TYPE:SKIRT" in line:
  615. modified_data += fan_sp_skirt + t0_fan + "\n"
  616. elif ";TYPE:WALL-INNER" in line:
  617. modified_data += fan_sp_wall_inner + t0_fan + "\n"
  618. elif ";TYPE:WALL-OUTER" in line:
  619. modified_data += fan_sp_wall_outer + t0_fan + "\n"
  620. elif ";TYPE:FILL" in line:
  621. modified_data += fan_sp_fill + t0_fan + "\n"
  622. elif ";TYPE:SKIN" in line:
  623. modified_data += fan_sp_skin + t0_fan + "\n"
  624. elif line == ";TYPE:SUPPORT":
  625. modified_data += fan_sp_support + t0_fan + "\n"
  626. elif ";TYPE:SUPPORT-INTERFACE" in line:
  627. modified_data += fan_sp_support_interface + t0_fan + "\n"
  628. elif ";MESH:NONMESH" in line:
  629. if feature_fan_combing == True:
  630. modified_data += "M106 S0" + t0_fan + "\n"
  631. elif ";TYPE:PRIME-TOWER" in line:
  632. modified_data += fan_sp_prime_tower + t0_fan + "\n"
  633. elif line == ";BRIDGE":
  634. modified_data += fan_sp_bridge + t0_fan + "\n"
  635. modified_data += line + "\n"
  636. ## If an End Layer is defined and is less than the last layer then insert the Final Speed
  637. if line == ";LAYER:" + str(the_end_layer) and the_end_is_enabled == True:
  638. modified_data += fan_sp_feature_final + t0_fan + "\n"
  639. if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
  640. single_fan_data[l_index] = modified_data
  641. return single_fan_data
  642. # Multi-fan by feature------------------------------------------------
  643. 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, fan_sp_skirt: str, fan_sp_wall_inner: str, fan_sp_wall_outer: str, fan_sp_fill: str, fan_sp_skin: str, fan_sp_support: str, fan_sp_support_interface: str, feature_fan_combing: str, fan_sp_prime_tower: str, fan_sp_bridge: str, fan_sp_feature_final: str)->str:
  644. multi_fan_data = data
  645. layer_number = "0"
  646. start_index = 1
  647. prev_fan = t0_fan
  648. this_fan = t0_fan
  649. modified_data = ""
  650. current_fan_speed = "0"
  651. for my_index in range(1, len(multi_fan_data) - 1, 1):
  652. layer = multi_fan_data[my_index]
  653. if ";LAYER:" + str(the_start_layer) + "\n" in layer:
  654. start_index = int(my_index) - 1
  655. break
  656. ## Track the previous tool changes
  657. for num in range(1,start_index,1):
  658. layer = multi_fan_data[num]
  659. lines = layer.split("\n")
  660. for line in lines:
  661. if line == "T0":
  662. prev_fan = this_fan
  663. this_fan = t0_fan
  664. elif line == "T1":
  665. prev_fan = this_fan
  666. this_fan = t1_fan
  667. elif line == "T2":
  668. prev_fan = this_fan
  669. this_fan = t2_fan
  670. elif line == "T3":
  671. prev_fan = this_fan
  672. this_fan = t3_fan
  673. ## Get the current tool.
  674. for l_index in range(start_index,start_index + 1,1):
  675. layer = multi_fan_data[l_index]
  676. lines = layer.split("\n")
  677. for line in lines:
  678. if line.startswith("T"):
  679. if line == "T0": this_fan = t0_fan
  680. if line == "T1": this_fan = t1_fan
  681. if line == "T2": this_fan = t2_fan
  682. if line == "T3": this_fan = t3_fan
  683. prev_fan = this_fan
  684. ## Start to make insertions-------------------------------------
  685. for l_index in range(start_index+1,len(multi_fan_data)-1,1):
  686. layer = multi_fan_data[l_index]
  687. lines = layer.split("\n")
  688. for line in lines:
  689. if line.startswith("T"):
  690. if line == "T0": this_fan = t0_fan
  691. if line == "T1": this_fan = t1_fan
  692. if line == "T2": this_fan = t2_fan
  693. if line == "T3": this_fan = t3_fan
  694. ## Turn off the prev fan
  695. modified_data += "M106 S0" + prev_fan + "\n"
  696. modified_data += line + "\n"
  697. ## Turn on the current fan
  698. modified_data += "M106 S" + str(current_fan_speed) + this_fan + "\n"
  699. prev_fan = this_fan
  700. if ";LAYER:" in line:
  701. layer_number = str(line.split(":")[1])
  702. modified_data += line + "\n"
  703. if int(layer_number) >= int(the_start_layer): # Problem with oneatatime < start
  704. if ";TYPE:SKIRT" in line:
  705. modified_data += line + "\n"
  706. modified_data += fan_sp_skirt + this_fan + "\n"
  707. current_fan_speed = str(fan_sp_skirt.split("S")[1])
  708. elif ";TYPE:WALL-INNER" in line:
  709. modified_data += line + "\n"
  710. modified_data += fan_sp_wall_inner + this_fan + "\n"
  711. current_fan_speed = str(fan_sp_wall_inner.split("S")[1])
  712. elif ";TYPE:WALL-OUTER" in line:
  713. modified_data += line + "\n"
  714. modified_data += fan_sp_wall_outer + this_fan + "\n"
  715. current_fan_speed = str(fan_sp_wall_outer.split("S")[1])
  716. elif ";TYPE:FILL" in line:
  717. modified_data += line + "\n"
  718. modified_data += fan_sp_fill + this_fan + "\n"
  719. current_fan_speed = str(fan_sp_fill.split("S")[1])
  720. elif ";TYPE:SKIN" in line:
  721. modified_data += line + "\n"
  722. modified_data += fan_sp_skin + this_fan + "\n"
  723. current_fan_speed = str(fan_sp_skin.split("S")[1])
  724. elif line == ";TYPE:SUPPORT":
  725. modified_data += line + "\n"
  726. modified_data += fan_sp_support + this_fan + "\n"
  727. current_fan_speed = str(fan_sp_support.split("S")[1])
  728. elif ";TYPE:SUPPORT-INTERFACE" in line:
  729. modified_data += line + "\n"
  730. modified_data += fan_sp_support_interface + this_fan + "\n"
  731. current_fan_speed = str(fan_sp_support_interface.split("S")[1])
  732. elif ";MESH:NONMESH" in line:
  733. if feature_fan_combing == True:
  734. modified_data += line + "\n"
  735. modified_data += "M106 S0" + this_fan + "\n"
  736. current_fan_speed = "0"
  737. else:
  738. modified_data += line + "\n"
  739. elif ";TYPE:PRIME-TOWER" in line:
  740. modified_data += line + "\n"
  741. modified_data += fan_sp_prime_tower + this_fan + "\n"
  742. current_fan_speed = str(fan_sp_prime_tower.split("S")[1])
  743. elif line == ";BRIDGE":
  744. modified_data += line + "\n"
  745. modified_data += fan_sp_bridge + this_fan + "\n"
  746. current_fan_speed = str(fan_sp_bridge.split("S")[1])
  747. ## If an end layer is defined - Insert the final speed and set the other variables to Final Speed to finish the file
  748. ## There cannot be a break here because if there are multiple fan numbers they still need to be shut off and turned on.
  749. elif line == ";LAYER:" + str(the_end_layer):
  750. modified_data += fan_sp_feature_final + this_fan + "\n"
  751. fan_sp_skirt = fan_sp_feature_final
  752. fan_sp_wall_inner = fan_sp_feature_final
  753. fan_sp_wall_outer = fan_sp_feature_final
  754. fan_sp_fill = fan_sp_feature_final
  755. fan_sp_skin = fan_sp_feature_final
  756. fan_sp_support = fan_sp_feature_final
  757. fan_sp_support_interface = fan_sp_feature_final
  758. fan_sp_prime_tower = fan_sp_feature_final
  759. fan_sp_bridge = fan_sp_feature_final
  760. else:
  761. ## Layer and Tool get inserted into modified_data above. All other lines go into modified_data here
  762. if not line.startswith("T") and not line.startswith(";LAYER:"): modified_data += line + "\n"
  763. if modified_data.endswith("\n"): modified_data = modified_data[0: - 1]
  764. multi_fan_data[l_index] = modified_data
  765. modified_data = ""
  766. return multi_fan_data
  767. #Try to catch layer input errors, set the minimum speed to 12%, and put the strings together
  768. def _layer_checker(self, fan_string: str, ty_pe: str, fan_mode: bool) -> str:
  769. fan_string_l = str(fan_string.split("/")[0])
  770. try:
  771. if int(fan_string_l) <= 1: fan_string_l = "1"
  772. if fan_string_l == "": fan_string_l = str(len(data))
  773. except ValueError:
  774. fan_string_l = str(len(data))
  775. fan_string_l = str(int(fan_string_l) - 1)
  776. fan_string_p = str(fan_string.split("/")[1])
  777. if fan_string_p == "": fan_string_p = "0"
  778. try:
  779. if int(fan_string_p) < 0: fan_string_p = "0"
  780. if int(fan_string_p) > 100: fan_string_p = "100"
  781. except ValueError:
  782. fan_string_p = "0"
  783. ## Set the minimum fan speed to 12%
  784. if int(fan_string_p) < 12 and int(fan_string_p) != 0:
  785. fan_string_p = "12"
  786. fan_layer_line = str(fan_string_l)
  787. if fan_mode:
  788. fan_percent_line = "M106 S" + str(round(int(fan_string_p) * 2.55))
  789. else:
  790. fan_percent_line = "M106 S" + str(round(int(fan_string_p) / 100, 1))
  791. if ty_pe == "l":
  792. return str(fan_layer_line)
  793. elif ty_pe == "p":
  794. return fan_percent_line
  795. #Try to catch feature input errors, set the minimum speed to 12%, and put the strings together when 'By Feature'
  796. def _feature_checker(self, fan_feat_string: int, fan_mode: bool) -> str:
  797. if fan_feat_string < 0: fan_feat_string = 0
  798. ## Set the minimum fan speed to 12%
  799. if fan_feat_string > 0 and fan_feat_string < 12: fan_feat_string = 12
  800. if fan_feat_string > 100: fan_feat_string = 100
  801. if fan_mode:
  802. fan_sp_feat = "M106 S" + str(round(fan_feat_string * 2.55))
  803. else:
  804. fan_sp_feat = "M106 S" + str(round(fan_feat_string / 100, 1))
  805. return fan_sp_feat
  806. # Add additional travel comments to turn the fan off during combing.
  807. def _add_travel_comment(self, the_data: str, lay_0_index: str) -> str:
  808. for lay_num in range(int(lay_0_index), len(the_data)-1,1):
  809. layer = the_data[lay_num]
  810. lines = layer.split("\n")
  811. ## Copy the data to new_data and make the insertions there
  812. new_data = lines
  813. g0_count = 0
  814. g0_index = -1
  815. feature_type = ";TYPE:SUPPORT"
  816. is_travel = False
  817. for index, line in enumerate(lines):
  818. insert_index = 0
  819. if ";TYPE:" in line:
  820. feature_type = line
  821. is_travel = False
  822. g0_count = 0
  823. if ";MESH:NONMESH" in line:
  824. is_travel = True
  825. g0_count = 0
  826. if line.startswith("G0 ") and not is_travel:
  827. g0_count += 1
  828. if g0_index == -1:
  829. g0_index = lines.index(line)
  830. elif not line.startswith("G0 ") and not is_travel:
  831. ## Add additional 'NONMESH' lines to shut the fan off during long combing moves--------
  832. if g0_count > 5:
  833. if not is_travel:
  834. new_data.insert(g0_index + insert_index, ";MESH:NONMESH")
  835. insert_index += 1
  836. ## Add the feature_type at the end of the combing move to turn the fan back on
  837. new_data.insert(g0_index + g0_count + 1, feature_type)
  838. insert_index += 1
  839. g0_count = 0
  840. g0_index = -1
  841. is_travel = False
  842. elif g0_count <= 5:
  843. g0_count = 0
  844. g0_index = -1
  845. is_travel = False
  846. the_data[lay_num] = "\n".join(new_data)
  847. return the_data